import {
  Injectable,
  Inject,
  NotFoundException,
  BadRequestException,
  ConflictException,
  Logger,
} from '@nestjs/common';
import { DataSource, EntityManager } from 'typeorm';
import {
  PaymentEntity,
  PaymentMethod,
  PaymentStatus,
} from '../../domain/entities/payment.entity';
import { OrderStatus } from '../../domain/entities/order.entity';
import {
  IPaymentRepository,
  PAYMENT_REPOSITORY,
} from '../../domain/repositories/payment.repository.interface';
import { OrderService } from './order.service';
import { AlipayAdapter } from '../../adapters/outbound/payment-methods/alipay.adapter';
import { WechatPayAdapter } from '../../adapters/outbound/payment-methods/wechat-pay.adapter';
import { StripeAdapter } from '../../adapters/outbound/payment-methods/stripe.adapter';
import { TransactionService } from '../../infrastructure/database/transaction.service';
import { PaymentORM } from '../../infrastructure/database/postgres/entities/payment.orm';
import { OrderORM } from '../../infrastructure/database/postgres/entities/order.orm';

export interface CreatePaymentParams {
  orderId: string;
  method: PaymentMethod;
}

export interface PaymentResult {
  paymentId: string;
  orderId: string;
  qrCodeUrl?: string;
  paymentUrl?: string;
  expiresAt: Date;
  method: PaymentMethod;
  amount: number;
}

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

  constructor(
    @Inject(PAYMENT_REPOSITORY)
    private readonly paymentRepo: IPaymentRepository,
    private readonly orderService: OrderService,
    private readonly alipayAdapter: AlipayAdapter,
    private readonly wechatPayAdapter: WechatPayAdapter,
    private readonly stripeAdapter: StripeAdapter,
    private readonly transactionService: TransactionService,
    private readonly dataSource: DataSource,
  ) {}

  async createPayment(params: CreatePaymentParams): Promise<PaymentResult> {
    const order = await this.orderService.findById(params.orderId);

    if (!order.canBePaid()) {
      throw new BadRequestException('Cannot create payment for this order');
    }

    // Check for existing pending payment
    const existingPayment = await this.paymentRepo.findPendingByOrderId(
      params.orderId,
    );

    if (existingPayment && !existingPayment.isExpired()) {
      return {
        paymentId: existingPayment.id,
        orderId: existingPayment.orderId,
        qrCodeUrl: existingPayment.qrCodeUrl || undefined,
        paymentUrl: existingPayment.paymentUrl || undefined,
        expiresAt: existingPayment.expiresAt,
        method: existingPayment.method,
        amount: existingPayment.amount,
      };
    }

    // Create payment via adapter
    let qrCodeUrl: string | undefined;
    let paymentUrl: string | undefined;

    switch (params.method) {
      case PaymentMethod.ALIPAY:
        const alipayResult = await this.alipayAdapter.createPayment(order);
        qrCodeUrl = alipayResult.qrCodeUrl;
        break;

      case PaymentMethod.WECHAT:
        const wechatResult = await this.wechatPayAdapter.createPayment(order);
        qrCodeUrl = wechatResult.qrCodeUrl;
        break;

      case PaymentMethod.CREDIT_CARD:
        const stripeResult = await this.stripeAdapter.createPayment(order);
        paymentUrl = stripeResult.paymentUrl;
        break;

      default:
        throw new BadRequestException('Unsupported payment method');
    }

    // Create payment entity
    const payment = PaymentEntity.create({
      orderId: order.id,
      method: params.method,
      amount: order.amount,
      currency: order.currency,
      qrCodeUrl,
      paymentUrl,
    });

    // 使用事务确保原子性：保存支付记录 + 更新订单状态
    const savedPayment = await this.transactionService.runInTransaction(
      async (manager: EntityManager) => {
        // 保存支付记录
        const paymentORM = this.toPaymentORM(payment);
        await manager.save(PaymentORM, paymentORM);

        // 更新订单状态
        await manager.update(OrderORM, order.id, {
          status: OrderStatus.PENDING_PAYMENT,
        });

        this.logger.log(
          `Payment created and order updated in transaction: paymentId=${payment.id}, orderId=${order.id}`,
        );

        return payment;
      },
    );

    return {
      paymentId: savedPayment.id,
      orderId: savedPayment.orderId,
      qrCodeUrl: savedPayment.qrCodeUrl || undefined,
      paymentUrl: savedPayment.paymentUrl || undefined,
      expiresAt: savedPayment.expiresAt,
      method: savedPayment.method,
      amount: savedPayment.amount,
    };
  }

  async findById(paymentId: string): Promise<PaymentEntity> {
    const payment = await this.paymentRepo.findById(paymentId);

    if (!payment) {
      throw new NotFoundException('Payment not found');
    }

    return payment;
  }

  /**
   * 处理支付回调 - 使用事务和幂等性保证
   *
   * 关键保障：
   * 1. 幂等性：通过 transaction_id 唯一索引防止重复处理
   * 2. 事务：Payment 和 Order 更新在同一事务中
   * 3. 乐观锁：version 列防止并发更新冲突
   */
  async handleCallback(
    method: PaymentMethod,
    payload: Record<string, unknown>,
  ): Promise<void> {
    let orderId: string;
    let transactionId: string;
    let success: boolean;

    switch (method) {
      case PaymentMethod.ALIPAY:
        const alipayResult = await this.alipayAdapter.handleCallback(payload);
        orderId = alipayResult.orderId;
        transactionId = alipayResult.transactionId;
        success = alipayResult.success;
        break;

      case PaymentMethod.WECHAT:
        const wechatResult = await this.wechatPayAdapter.handleCallback(payload);
        orderId = wechatResult.orderId;
        transactionId = wechatResult.transactionId;
        success = wechatResult.success;
        break;

      case PaymentMethod.CREDIT_CARD:
        const stripeResult = await this.stripeAdapter.handleCallback(payload);
        orderId = stripeResult.orderId;
        transactionId = stripeResult.transactionId;
        success = stripeResult.success;
        break;

      default:
        throw new BadRequestException('Unsupported payment method');
    }

    // 幂等性检查：是否已处理过此 transactionId
    const existingByTxId =
      await this.paymentRepo.findByTransactionId(transactionId);
    if (existingByTxId) {
      this.logger.warn(
        `Duplicate callback ignored: transactionId=${transactionId}`,
      );
      return; // 已处理过，直接返回（幂等）
    }

    // 使用乐观锁事务处理，防止并发问题
    await this.transactionService.runWithOptimisticLock(
      async (manager: EntityManager) => {
        // 在事务中重新查询，获取最新版本
        const paymentORM = await manager.findOne(PaymentORM, {
          where: { orderId, status: PaymentStatus.PENDING },
          lock: { mode: 'pessimistic_write' }, // 悲观锁，防止并发
        });

        if (!paymentORM) {
          throw new NotFoundException(
            `Pending payment not found for order: ${orderId}`,
          );
        }

        const now = new Date();

        if (success) {
          // 更新支付状态
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          await manager.update(PaymentORM, paymentORM.id, {
            status: PaymentStatus.COMPLETED,
            transactionId,
            paidAt: now,
            callbackPayload: payload,
          } as any);

          // 更新订单状态
          await manager.update(OrderORM, orderId, {
            status: OrderStatus.PAID,
            paymentId: paymentORM.id,
            paymentMethod: method,
            paidAt: now,
          });

          this.logger.log(
            `Payment completed in transaction: paymentId=${paymentORM.id}, orderId=${orderId}, transactionId=${transactionId}`,
          );
        } else {
          // 支付失败
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          await manager.update(PaymentORM, paymentORM.id, {
            status: PaymentStatus.FAILED,
            failedReason: 'Payment failed',
            callbackPayload: payload,
          } as any);

          this.logger.warn(
            `Payment failed: paymentId=${paymentORM.id}, orderId=${orderId}`,
          );
        }
      },
      3, // 最多重试 3 次
    );
  }

  async checkStatus(
    paymentId: string,
  ): Promise<{ status: PaymentStatus; paidAt?: Date }> {
    const payment = await this.findById(paymentId);
    return {
      status: payment.status,
      paidAt: payment.paidAt || undefined,
    };
  }

  private toPaymentORM(entity: PaymentEntity): Partial<PaymentORM> {
    return {
      id: entity.id,
      orderId: entity.orderId,
      method: entity.method,
      amount: entity.amount,
      currency: entity.currency,
      status: entity.status,
      qrCodeUrl: entity.qrCodeUrl,
      paymentUrl: entity.paymentUrl,
      expiresAt: entity.expiresAt,
      paidAt: entity.paidAt,
      transactionId: entity.transactionId,
      failedReason: entity.failedReason,
      callbackPayload: entity.callbackPayload,
    };
  }
}
