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

  private readonly tmnCode: string;
  private readonly hashSecret: string;
  private readonly baseUrl: string;
  private readonly apiUrl: string;

  constructor(
    private readonly config: ConfigService,
    private readonly logger: LoggerService,
  ) {
    this.tmnCode = this.config.getOrThrow<string>('VNPAY_TMN_CODE');
    this.hashSecret = this.config.getOrThrow<string>('VNPAY_HASH_SECRET');
    this.baseUrl = this.config.get<string>('VNPAY_BASE_URL', 'https://sandbox.vnpayment.vn/paymentv2/vpcpay.html');
    this.apiUrl = this.config.get<string>('VNPAY_API_URL', 'https://sandbox.vnpayment.vn/merchant_webapi/api/transaction');
  }

  async createPaymentUrl(params: CreatePaymentUrlParams): Promise<CreatePaymentUrlResult> {
    const now = new Date();
    const createDate = this.formatDate(now);
    const expireDate = this.formatDate(new Date(now.getTime() + 15 * 60 * 1000));

    const vnpParams: Record<string, string> = {
      vnp_Version: '2.1.0',
      vnp_Command: 'pay',
      vnp_TmnCode: this.tmnCode,
      vnp_Locale: 'vn',
      vnp_CurrCode: 'VND',
      vnp_TxnRef: params.orderId,
      vnp_OrderInfo: params.description,
      vnp_OrderType: 'other',
      vnp_Amount: (params.amountVND * 100n).toString(),
      vnp_ReturnUrl: params.returnUrl,
      vnp_IpAddr: params.ipAddress,
      vnp_CreateDate: createDate,
      vnp_ExpireDate: expireDate,
    };

    const sortedParams = this.sortObject(vnpParams);
    const signData = new URLSearchParams(sortedParams).toString();
    const hmac = crypto.createHmac('sha512', this.hashSecret);
    const signed = hmac.update(Buffer.from(signData, 'utf-8')).digest('hex');

    sortedParams['vnp_SecureHash'] = signed;

    const paymentUrl = `${this.baseUrl}?${new URLSearchParams(sortedParams).toString()}`;

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

    return {
      paymentUrl,
      providerTxId: params.orderId,
    };
  }

  verifyCallback(data: Record<string, string>): CallbackVerifyResult {
    const secureHash = data['vnp_SecureHash'];
    const orderId = data['vnp_TxnRef'] ?? '';
    const providerTxId = data['vnp_TransactionNo'] ?? '';

    const verifyParams = { ...data };
    delete verifyParams['vnp_SecureHash'];
    delete verifyParams['vnp_SecureHashType'];

    const sortedParams = this.sortObject(verifyParams);
    const signData = new URLSearchParams(sortedParams).toString();
    const hmac = crypto.createHmac('sha512', this.hashSecret);
    const checkSum = hmac.update(Buffer.from(signData, 'utf-8')).digest('hex');

    const isValid =
      secureHash != null &&
      checkSum.length === secureHash.length &&
      crypto.timingSafeEqual(Buffer.from(secureHash, 'hex'), Buffer.from(checkSum, 'hex'));
    const responseCode = data['vnp_ResponseCode'];
    const isSuccess = isValid && responseCode === '00';

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

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

  async refund(params: RefundParams): Promise<RefundResult> {
    const now = new Date();
    const requestId = crypto.randomUUID();

    const refundData: Record<string, string> = {
      vnp_RequestId: requestId,
      vnp_Version: '2.1.0',
      vnp_Command: 'refund',
      vnp_TmnCode: this.tmnCode,
      vnp_TransactionType: '02',
      vnp_TxnRef: params.providerTxId,
      vnp_Amount: (params.amountVND * 100n).toString(),
      vnp_OrderInfo: params.reason,
      vnp_TransactionDate: this.formatDate(now),
      vnp_CreateDate: this.formatDate(now),
      vnp_CreateBy: 'system',
      vnp_IpAddr: '127.0.0.1',
    };

    const signData = [
      refundData['vnp_RequestId'],
      refundData['vnp_Version'],
      refundData['vnp_Command'],
      refundData['vnp_TmnCode'],
      refundData['vnp_TransactionType'],
      refundData['vnp_TxnRef'],
      refundData['vnp_Amount'],
      refundData['vnp_TransactionDate'],
      refundData['vnp_CreateBy'],
      refundData['vnp_CreateDate'],
      refundData['vnp_IpAddr'],
      refundData['vnp_OrderInfo'],
    ].join('|');

    const hmac = crypto.createHmac('sha512', this.hashSecret);
    refundData['vnp_SecureHash'] = hmac
      .update(Buffer.from(signData, 'utf-8'))
      .digest('hex');

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

      const result = await response.json() as Record<string, string>;
      const success = result['vnp_ResponseCode'] === '00';

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

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

  private formatDate(date: Date): string {
    return date
      .toISOString()
      .replace(/[-:T]/g, '')
      .slice(0, 14);
  }

  private sortObject(obj: Record<string, string>): Record<string, string> {
    const sorted: Record<string, string> = {};
    const keys = Object.keys(obj).sort();
    for (const key of keys) {
      sorted[key] = obj[key]!;
    }
    return sorted;
  }
}
