import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { PaymentEntity, PaymentMethod, PaymentStatus } from '../domain/entities/payment.entity';
import { OrderService } from '../order/order.service';
import { OrderStatus } from '../domain/entities/order.entity';
import { AlipayAdapter } from './adapters/alipay.adapter';
import { WechatPayAdapter } from './adapters/wechat-pay.adapter';
import { StripeAdapter } from './adapters/stripe.adapter';

export interface CreatePaymentDto {
  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 {
  constructor(
    @InjectRepository(PaymentEntity)
    private paymentRepo: Repository<PaymentEntity>,
    private orderService: OrderService,
    private alipayAdapter: AlipayAdapter,
    private wechatPayAdapter: WechatPayAdapter,
    private stripeAdapter: StripeAdapter,
  ) {}

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

    if (order.status !== OrderStatus.CREATED && order.status !== OrderStatus.PENDING_PAYMENT) {
      throw new BadRequestException('Cannot create payment for this order');
    }

    // Check for existing pending payment
    const existingPayment = await this.paymentRepo.findOne({
      where: {
        orderId: dto.orderId,
        status: PaymentStatus.PENDING,
      },
    });

    if (existingPayment && existingPayment.expiresAt > new Date()) {
      return {
        paymentId: existingPayment.id,
        orderId: existingPayment.orderId,
        qrCodeUrl: existingPayment.qrCodeUrl,
        paymentUrl: existingPayment.paymentUrl,
        expiresAt: existingPayment.expiresAt,
        method: existingPayment.method,
        amount: Number(existingPayment.amount),
      };
    }

    // Create payment via adapter
    const expiresAt = new Date(Date.now() + 30 * 60 * 1000); // 30 minutes
    let qrCodeUrl: string | undefined;
    let paymentUrl: string | undefined;

    switch (dto.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');
    }

    // Save payment record
    const payment = this.paymentRepo.create({
      orderId: order.id,
      method: dto.method,
      amount: order.amount,
      currency: order.currency,
      status: PaymentStatus.PENDING,
      qrCodeUrl,
      paymentUrl,
      expiresAt,
    });

    const savedPayment = await this.paymentRepo.save(payment);

    // Update order status
    await this.orderService.updateStatus(order.id, OrderStatus.PENDING_PAYMENT);

    return {
      paymentId: savedPayment.id,
      orderId: savedPayment.orderId,
      qrCodeUrl: savedPayment.qrCodeUrl,
      paymentUrl: savedPayment.paymentUrl,
      expiresAt: savedPayment.expiresAt,
      method: savedPayment.method,
      amount: Number(savedPayment.amount),
    };
  }

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

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

    return payment;
  }

  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');
    }

    // Find payment by order ID
    const payment = await this.paymentRepo.findOne({
      where: { orderId, status: PaymentStatus.PENDING },
    });

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

    // Update payment
    payment.transactionId = transactionId;
    payment.callbackPayload = payload;

    if (success) {
      payment.status = PaymentStatus.COMPLETED;
      payment.paidAt = new Date();
      await this.paymentRepo.save(payment);

      // Update order
      await this.orderService.markAsPaid(orderId, payment.id, method);
    } else {
      payment.status = PaymentStatus.FAILED;
      payment.failedReason = 'Payment failed';
      await this.paymentRepo.save(payment);
    }
  }

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