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

export interface TelebirrWebhookPayload {
  merch_order_id: string;
  payment_order_id: string;
  trade_status: string;
  trans_id?: string;
  total_amount?: string;
  trans_currency?: string;
  notify_time?: string;
  trans_end_time?: string;
  sign: string;
  sign_type?: string;
  [key: string]: unknown;
}

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

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

  async handle(payload: TelebirrWebhookPayload): Promise<void> {
    const merchantOrderId = payload.merch_order_id;
    const externalEventId = this.buildExternalEventId(payload);
    const signatureValid = this.provider.verifyWebhookSignature(
      payload as unknown as Record<string, unknown>,
    );

    const eventRow = await this.persistEvent({
      externalEventId,
      merchantOrderId,
      providerTxnId: payload.trans_id ?? payload.payment_order_id,
      signatureValid,
      status: payload.trade_status,
      payload,
    });

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

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

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

    const mapped = this.provider.mapWebhookTradeStatus(payload.trade_status);

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

  private buildExternalEventId(payload: TelebirrWebhookPayload): string {
    return `${payload.payment_order_id}_${payload.trade_status}`;
  }

  private async persistEvent(input: {
    externalEventId: string;
    merchantOrderId: string;
    providerTxnId?: string;
    signatureValid: boolean;
    status: string;
    payload: TelebirrWebhookPayload;
  }): Promise<{ id: string } | null> {
    try {
      return await this.prisma.paymentWebhookEvent.create({
        data: {
          provider: PaymentMethodType.TELEBIRR,
          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 },
    });
  }

  private parseEpochSeconds(raw: string | undefined): Date | undefined {
    if (!raw) return undefined;
    const n = parseInt(raw, 10);
    if (Number.isNaN(n)) return undefined;
    return new Date(n * 1000);
  }
}
