import * as crypto from 'crypto';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { type PaymentProvider } from '@prisma/client';
import { LoggerService } from '@modules/shared';
import {
  type IPaymentGateway,
  type CreatePaymentUrlParams,
  type CreatePaymentUrlResult,
  type CallbackVerifyResult,
  type RefundParams,
  type RefundResult,
} from './payment-gateway.interface';

@Injectable()
export class MomoService implements IPaymentGateway {
  readonly provider: PaymentProvider = 'MOMO';

  private readonly partnerCode: string;
  private readonly accessKey: string;
  private readonly secretKey: string;
  private readonly endpoint: string;
  private readonly callbackBaseUrl: string;

  constructor(
    private readonly config: ConfigService,
    private readonly logger: LoggerService,
  ) {
    this.partnerCode = this.config.getOrThrow<string>('MOMO_PARTNER_CODE');
    this.accessKey = this.config.getOrThrow<string>('MOMO_ACCESS_KEY');
    this.secretKey = this.config.getOrThrow<string>('MOMO_SECRET_KEY');
    this.endpoint = this.config.get<string>('MOMO_ENDPOINT', 'https://test-payment.momo.vn/v2/gateway/api');
    this.callbackBaseUrl = this.config.get<string>('PAYMENT_CALLBACK_BASE_URL', 'https://api.goodgo.vn');
  }

  async createPaymentUrl(params: CreatePaymentUrlParams): Promise<CreatePaymentUrlResult> {
    const requestId = crypto.randomUUID();
    const requestType = 'payWithMethod';
    const extraData = '';
    const autoCapture = true;
    const lang = 'vi';
    const amount = params.amountVND.toString();

    // ipnUrl must be the backend server-to-server callback endpoint (never the frontend URL)
    const ipnUrl = params.callbackUrl ?? `${this.callbackBaseUrl}/api/v1/payments/callback/momo`;

    const rawSignature = [
      `accessKey=${this.accessKey}`,
      `amount=${amount}`,
      `extraData=${extraData}`,
      `ipnUrl=${ipnUrl}`,
      `orderId=${params.orderId}`,
      `orderInfo=${params.description}`,
      `partnerCode=${this.partnerCode}`,
      `redirectUrl=${params.returnUrl}`,
      `requestId=${requestId}`,
      `requestType=${requestType}`,
    ].join('&');

    const signature = crypto
      .createHmac('sha256', this.secretKey)
      .update(rawSignature)
      .digest('hex');

    const body = {
      partnerCode: this.partnerCode,
      partnerName: 'GoodGo',
      storeId: 'GoodGo',
      requestId,
      amount: Number(amount),
      orderId: params.orderId,
      orderInfo: params.description,
      redirectUrl: params.returnUrl,
      ipnUrl,
      lang,
      requestType,
      autoCapture,
      extraData,
      signature,
    };

    try {
      const response = await fetch(`${this.endpoint}/create`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      });

      const result = await response.json() as { resultCode: number; payUrl: string };

      if (result.resultCode !== 0) {
        throw new Error(`MoMo create payment failed: resultCode=${result.resultCode}`);
      }

      this.logger.log(`MoMo payment URL created for order ${params.orderId}`, 'MomoService');

      return {
        paymentUrl: result.payUrl,
        providerTxId: params.orderId,
      };
    } catch (error) {
      this.logger.error(`MoMo createPaymentUrl error: ${error}`, undefined, 'MomoService');
      throw error;
    }
  }

  verifyCallback(data: Record<string, string>): CallbackVerifyResult {
    const orderId = data['orderId'] ?? '';
    const providerTxId = data['transId'] ?? '';
    const resultCode = data['resultCode'];
    const receivedSignature = data['signature'] ?? '';

    const rawSignature = [
      `accessKey=${this.accessKey}`,
      `amount=${data['amount']}`,
      `extraData=${data['extraData'] ?? ''}`,
      `message=${data['message'] ?? ''}`,
      `orderId=${orderId}`,
      `orderInfo=${data['orderInfo'] ?? ''}`,
      `orderType=${data['orderType'] ?? ''}`,
      `partnerCode=${this.partnerCode}`,
      `payType=${data['payType'] ?? ''}`,
      `requestId=${data['requestId'] ?? ''}`,
      `responseTime=${data['responseTime'] ?? ''}`,
      `resultCode=${resultCode}`,
      `transId=${providerTxId}`,
    ].join('&');

    const expectedSignature = crypto
      .createHmac('sha256', this.secretKey)
      .update(rawSignature)
      .digest('hex');

    const isValid =
      receivedSignature.length > 0 &&
      receivedSignature.length === expectedSignature.length &&
      crypto.timingSafeEqual(Buffer.from(receivedSignature, 'hex'), Buffer.from(expectedSignature, 'hex'));
    const isSuccess = isValid && resultCode === '0';

    this.logger.log(
      `MoMo callback verified: orderId=${orderId}, valid=${isValid}, success=${isSuccess}`,
      'MomoService',
    );

    return {
      isValid,
      orderId,
      providerTxId,
      isSuccess,
      rawData: data,
    };
  }

  async refund(params: RefundParams): Promise<RefundResult> {
    const requestId = crypto.randomUUID();
    const amount = params.amountVND.toString();

    const rawSignature = [
      `accessKey=${this.accessKey}`,
      `amount=${amount}`,
      `description=${params.reason}`,
      `orderId=${requestId}`,
      `partnerCode=${this.partnerCode}`,
      `requestId=${requestId}`,
      `transId=${params.providerTxId}`,
    ].join('&');

    const signature = crypto
      .createHmac('sha256', this.secretKey)
      .update(rawSignature)
      .digest('hex');

    const body = {
      partnerCode: this.partnerCode,
      orderId: requestId,
      requestId,
      amount: Number(amount),
      transId: Number(params.providerTxId),
      lang: 'vi',
      description: params.reason,
      signature,
    };

    try {
      const response = await fetch(`${this.endpoint}/refund`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      });

      const result = await response.json() as { resultCode: number };
      const success = result.resultCode === 0;

      this.logger.log(
        `MoMo refund ${success ? 'successful' : 'failed'} for ${params.providerTxId}`,
        'MomoService',
      );

      return {
        success,
        refundTxId: success ? requestId : null,
      };
    } catch (error) {
      this.logger.error(`MoMo refund error: ${error}`, undefined, 'MomoService');
      return { success: false, refundTxId: null };
    }
  }
}
