import { Injectable, Logger } from "@nestjs/common";
import { ProviderMethod } from "@edr/types";
import { IntentsRepository } from "../intents/intents.repository";
import {
  IntentsService,
  ProviderResultInput,
} from "../intents/intents.service";
import { WebhookEventsRepository } from "./webhook-events.repository";

/** A provider webhook reduced to the fields the shared pipeline needs. */
export interface NormalizedWebhook {
  provider: ProviderMethod;
  /** Provider event id (or a deterministic derivation) — the dedupe key. */
  externalEventId: string;
  merchantOrderId: string;
  providerTxnId?: string;
  signatureValid: boolean;
  /** Raw provider status string, stored for audit. */
  rawStatus: string;
  payload: Record<string, unknown>;
  /** Mapped outcome to feed the intent state machine. */
  result: ProviderResultInput;
}

/**
 * The shared webhook pipeline every provider handler funnels into:
 * persist+dedupe → signature gate → intent lookup → prefix/service cross-check →
 * state machine → mark processed. Always returns (never throws) so controllers can
 * ack 200 fast — providers like Waafi time out at 5s and do not retry.
 */
@Injectable()
export class WebhookProcessorService {
  private readonly logger = new Logger(WebhookProcessorService.name);

  constructor(
    private readonly webhookEvents: WebhookEventsRepository,
    private readonly intentsRepository: IntentsRepository,
    private readonly intentsService: IntentsService,
  ) {}

  async process(webhook: NormalizedWebhook): Promise<void> {
    const { provider, merchantOrderId } = webhook;

    const eventRow = await this.webhookEvents.createDeduped({
      provider,
      externalEventId: webhook.externalEventId,
      merchantOrderId,
      providerTxnId: webhook.providerTxnId ?? null,
      signatureValid: webhook.signatureValid,
      status: webhook.rawStatus,
      payload: webhook.payload,
    });
    if (!eventRow) {
      this.logger.log(
        `${provider} webhook duplicate: ${webhook.externalEventId} — short-circuit OK`,
      );
      return;
    }

    if (!webhook.signatureValid) {
      this.logger.warn(
        `${provider} webhook signature invalid/stale for ref=${merchantOrderId}`,
      );
      await this.webhookEvents.markProcessed(eventRow.id, "signature-invalid");
      return;
    }

    const intent =
      await this.intentsRepository.findByMerchantOrderId(merchantOrderId);
    if (!intent) {
      // Tolerated: webhook may have raced the intent commit, or the reference is foreign.
      // The provider gets a 200; retry/poll/reconciliation converges later.
      this.logger.warn(
        `${provider} webhook: no PaymentIntent for ref=${merchantOrderId}`,
      );
      await this.webhookEvents.markProcessed(eventRow.id, "intent-not-found");
      return;
    }

    try {
      await this.intentsService.applyProviderResult(intent.id, webhook.result);
      await this.webhookEvents.markProcessed(eventRow.id);
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err);
      this.logger.error(
        `${provider} webhook processing failed for ${merchantOrderId}: ${message}`,
      );
      await this.webhookEvents.markProcessed(
        eventRow.id,
        `processing-error: ${message}`,
      );
    }
  }
}
