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 ZalopayService implements IPaymentGateway {
  readonly provider: PaymentProvider = 'ZALOPAY';

  private readonly appId: string;
  private readonly key1: string;
  private readonly key2: string;
  private readonly endpoint: string;
  private readonly callbackBaseUrl: string;

  constructor(
    private readonly config: ConfigService,
    private readonly logger: LoggerService,
  ) {
    this.appId = this.config.getOrThrow<string>('ZALOPAY_APP_ID');
    this.key1 = this.config.getOrThrow<string>('ZALOPAY_KEY1');
    this.key2 = this.config.getOrThrow<string>('ZALOPAY_KEY2');
    this.endpoint = this.config.get<string>('ZALOPAY_ENDPOINT', 'https://sb-openapi.zalopay.vn/v2');
    this.callbackBaseUrl = this.config.get<string>('PAYMENT_CALLBACK_BASE_URL', 'https://api.goodgo.vn');
  }

  async createPaymentUrl(params: CreatePaymentUrlParams): Promise<CreatePaymentUrlResult> {
    const now = new Date();
    const appTransId = `${this.formatYYMMDD(now)}_${params.orderId}`;
    const appTime = now.getTime();
    const amount = Number(params.amountVND);

    // embed_data carries the frontend redirect URL; callback_url is the backend IPN endpoint
    const embedData = JSON.stringify({ redirecturl: params.returnUrl });
    const callbackUrl = params.callbackUrl ?? `${this.callbackBaseUrl}/api/v1/payments/callback/zalopay`;
    const items = JSON.stringify([]);

    const data = [
      this.appId,
      appTransId,
      appTime,
      amount,
      embedData,
      items,
    ].join('|');

    const mac = crypto
      .createHmac('sha256', this.key1)
      .update(data)
      .digest('hex');

    const body = {
      app_id: Number(this.appId),
      app_trans_id: appTransId,
      app_user: params.orderId,
      app_time: appTime,
      amount,
      item: items,
      description: params.description,
      embed_data: embedData,
      callback_url: callbackUrl,
      mac,
    };

    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 {
        return_code: number;
        order_url: string;
        zp_trans_token: string;
      };

      if (result.return_code !== 1) {
        throw new Error(`ZaloPay create payment failed: return_code=${result.return_code}`);
      }

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

      return {
        paymentUrl: result.order_url,
        providerTxId: appTransId,
      };
    } catch (error) {
      this.logger.error(`ZaloPay createPaymentUrl error: ${error}`, undefined, 'ZalopayService');
      throw error;
    }
  }

  verifyCallback(data: Record<string, string>): CallbackVerifyResult {
    const dataStr = data['data'] ?? '';
    const reqMac = data['mac'] ?? '';

    const mac = crypto
      .createHmac('sha256', this.key2)
      .update(dataStr)
      .digest('hex');

    const isValid =
      reqMac.length > 0 &&
      reqMac.length === mac.length &&
      crypto.timingSafeEqual(Buffer.from(reqMac, 'hex'), Buffer.from(mac, 'hex'));

    let parsedData: Record<string, unknown> = {};
    let orderId = '';
    let providerTxId = '';

    if (isValid) {
      try {
        parsedData = JSON.parse(dataStr);
        orderId = String(parsedData['app_trans_id'] ?? '');
        providerTxId = String(parsedData['zp_trans_id'] ?? '');
      } catch {
        return {
          isValid: false,
          orderId: '',
          providerTxId: '',
          isSuccess: false,
          rawData: data,
        };
      }
    }

    this.logger.log(
      `ZaloPay callback verified: orderId=${orderId}, valid=${isValid}`,
      'ZalopayService',
    );

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

  async refund(params: RefundParams): Promise<RefundResult> {
    const now = Date.now();
    const mRefundId = `${this.formatYYMMDD(new Date())}_${this.appId}_${now}`;
    const amount = Number(params.amountVND);

    const data = [
      this.appId,
      params.providerTxId,
      amount,
      params.reason,
      now,
    ].join('|');

    const mac = crypto
      .createHmac('sha256', this.key1)
      .update(data)
      .digest('hex');

    const body = {
      app_id: Number(this.appId),
      zp_trans_id: params.providerTxId,
      m_refund_id: mRefundId,
      amount,
      timestamp: now,
      description: params.reason,
      mac,
    };

    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 { return_code: number };
      const success = result.return_code === 1;

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

      return {
        success,
        refundTxId: success ? mRefundId : null,
      };
    } catch (error) {
      this.logger.error(`ZaloPay refund error: ${error}`, undefined, 'ZalopayService');
      return { success: false, refundTxId: null };
    }
  }

  private formatYYMMDD(date: Date): string {
    const yy = date.getFullYear().toString().slice(-2);
    const mm = (date.getMonth() + 1).toString().padStart(2, '0');
    const dd = date.getDate().toString().padStart(2, '0');
    return `${yy}${mm}${dd}`;
  }
}
