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 { CardProvider } from '../providers/card.provider';

export interface CardWebhookPayload {
  id: string;
  type: string;
  data: {
    object: {
      id: string;
      status: string;
      amount: number;
      currency: string;
      metadata: {
        merchantOrderId: string;
        bookingRef: string;
      };
      transaction_id?: string;
      paid_at?: number;
      failure_code?: string;
      failure_message?: string;
    };
  };
  created: number;
}

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

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

  async handle(payload: CardWebhookPayload, signature: string): Promise<void> {
    const merchantOrderId = payload.data.object.metadata.merchantOrderId;
    const externalEventId = `${payload.id}_${payload.type}`;
    const signatureValid = this.provider.verifyWebhookSignature(
      payload as unknown as Record<string, unknown>,
      signature,
    );

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

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

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

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

    const mapped = this.provider.mapWebhookStatus(payload.data.object.status);

    try {
      if (mapped === PaymentIntentStatus.SUCCEEDED) {
        await this.payments.finalizePaymentSuccess({
          intentId: intent.id,
          providerTxnId: payload.data.object.transaction_id,
          paidAt: payload.data.object.paid_at ? new Date(payload.data.object.paid_at * 1000) : undefined,
        });
      } else if (mapped === PaymentIntentStatus.FAILED) {
        await this.payments.markPaymentFailed({
          intentId: intent.id,
          failureCode: payload.data.object.failure_code,
          failureMessage: payload.data.object.failure_message,
        });
      } else {
        await this.prisma.paymentIntent.update({
          where: { id: intent.id },
          data: {
            status: mapped,
            providerTxnId: payload.data.object.transaction_id ?? undefined,
          },
        });
      }
      await this.markProcessed(eventRow.id);
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err);
      this.logger.error(`Card 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: CardWebhookPayload;
  }): Promise<{ id: string } | null> {
    try {
      return await this.prisma.paymentWebhookEvent.create({
        data: {
          provider: PaymentMethodType.CARD,
          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 },
    });
  }
}
