import { Injectable, Logger } from '@nestjs/common';
import { Prisma, PaymentIntentStatus, PaymentMethodType } from '@prisma/client';
import { PrismaService } from '../../../common/prisma.service';
import { PaymentsService } from '../payments.service';
import { EBirrProvider } from '../providers/ebirr.provider';

export interface EBirrWebhookPayload {
  merchantCode: string;
  orderNo: string;
  tradeStatus: string;
  tradeNo?: string;
  totalAmount?: number;
  currency?: string;
  payTime?: number;
  timestamp: number;
  sign: string;
  [key: string]: unknown;
}

@Injectable()
export class EBirrWebhookService {
  private readonly logger = new Logger(EBirrWebhookService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly provider: EBirrProvider,
    private readonly payments: PaymentsService,
  ) {}

  async handle(payload: EBirrWebhookPayload): Promise<void> {
    const merchantOrderId = payload.orderNo;
    const externalEventId = `${payload.orderNo}_${payload.tradeStatus}_${payload.timestamp}`;
    const signatureValid = this.provider.verifyWebhookSignature(
      payload as unknown as Record<string, unknown>,
    );

    const eventRow = await this.persistEvent({
      externalEventId,
      merchantOrderId,
      providerTxnId: payload.tradeNo,
      signatureValid,
      status: payload.tradeStatus,
      payload,
    });

    if (!eventRow) {
      this.logger.log(`eBirr webhook duplicate: ${externalEventId} — short-circuit OK`);
      return;
    }

    if (!signatureValid) {
      this.logger.warn(`eBirr webhook signature invalid for orderNo=${merchantOrderId}`);
      await this.markProcessed(eventRow.id, 'signature-invalid');
      return;
    }

    const intent = await this.prisma.paymentIntent.findUnique({
      where: { merchantOrderId },
    });
    if (!intent) {
      this.logger.warn(`eBirr webhook: no PaymentIntent for orderNo=${merchantOrderId}`);
      await this.markProcessed(eventRow.id, 'intent-not-found');
      return;
    }

    const mapped = this.provider.mapWebhookStatus(payload.tradeStatus);

    try {
      if (mapped === PaymentIntentStatus.SUCCEEDED) {
        await this.payments.finalizePaymentSuccess({
          intentId: intent.id,
          providerTxnId: payload.tradeNo,
          paidAt: payload.payTime ? new Date(payload.payTime) : undefined,
        });
      } else if (mapped === PaymentIntentStatus.FAILED) {
        await this.payments.markPaymentFailed({
          intentId: intent.id,
          failureCode: payload.tradeStatus,
        });
      } else {
        await this.prisma.paymentIntent.update({
          where: { id: intent.id },
          data: { status: mapped, providerTxnId: payload.tradeNo ?? undefined },
        });
      }
      await this.markProcessed(eventRow.id);
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err);
      this.logger.error(`eBirr webhook processing failed for ${merchantOrderId}: ${message}`);
      await this.markProcessed(eventRow.id, `processing-error: ${message}`);
      throw err;
    }
  }

  private async persistEvent(input: {
    externalEventId: string;
    merchantOrderId: string;
    providerTxnId?: string;
    signatureValid: boolean;
    status: string;
    payload: EBirrWebhookPayload;
  }): Promise<{ id: string } | null> {
    try {
      return await this.prisma.paymentWebhookEvent.create({
        data: {
          provider: PaymentMethodType.EBIRR,
          externalEventId: input.externalEventId,
          merchantOrderId: input.merchantOrderId,
          providerTxnId: input.providerTxnId,
          signatureValid: input.signatureValid,
          status: input.status,
          payload: input.payload as unknown as Prisma.InputJsonValue,
        },
        select: { id: true },
      });
    } catch (err) {
      if (
        err instanceof Prisma.PrismaClientKnownRequestError &&
        err.code === 'P2002'
      ) {
        return null;
      }
      throw err;
    }
  }

  private async markProcessed(eventId: string, processingError?: string): Promise<void> {
    await this.prisma.paymentWebhookEvent.update({
      where: { id: eventId },
      data: { processedAt: new Date(), processingError },
    });
  }
}
