import { PaymentService, PaymentRequest, PaymentCallback, RefundRequest } from '../PaymentService';
import db from '../../config/database';
import { FinanceService } from '../FinanceService';

// 模拟依赖
jest.mock('../../config/database');
jest.mock('../FinanceService');

const mockDb = jest.mocked(db);
const mockFinanceService = jest.mocked(FinanceService);

describe('PaymentService', () => {
  beforeAll(async () => {
    // 初始化测试环境
    await PaymentService.initTable();
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  describe('createPayment', () => {
    it('should create a payment successfully', async () => {
      const paymentRequest: PaymentRequest = {
        tenantId: 'tenant-1',
        orderId: 'order-1',
        amount: 100,
        currency: 'USD',
        paymentMethod: 'ALIPAY',
        returnUrl: 'https://example.com/return',
        notifyUrl: 'https://example.com/notify',
        metadata: { user_id: 'user-1' }
      };

      // 模拟数据库插入
      (db as any).mockImplementation(() => ({
        insert: jest.fn().mockResolvedValue([1]),
      }) as any);

      const result = await PaymentService.createPayment(paymentRequest);

      expect(result.paymentId).toMatch(/PAY-\d+-\d+/);
      expect(result.status).toBe('PENDING');
      expect(result.redirectUrl).toBeDefined();
    });

    it('should handle creation failure', async () => {
      const paymentRequest: PaymentRequest = {
        tenantId: 'tenant-1',
        orderId: 'order-1',
        amount: 100,
        currency: 'USD',
        paymentMethod: 'ALIPAY',
        returnUrl: 'https://example.com/return',
        notifyUrl: 'https://example.com/notify'
      };

      // 模拟数据库错误
      (db as any).mockImplementation(() => ({
        insert: jest.fn().mockRejectedValue(new Error('Database error')),
      }) as any);

      const result = await PaymentService.createPayment(paymentRequest);

      expect(result.status).toBe('FAILED');
      expect(result.message).toBe('Database error');
    });
  });

  describe('handleCallback', () => {
    it('should handle successful payment callback', async () => {
      const callback: PaymentCallback = {
        paymentId: 'pay-1',
        orderId: 'order-1',
        status: 'SUCCESS',
        transactionId: 'trans-1',
        amount: 100,
        currency: 'USD',
        timestamp: new Date(),
        signature: 'signature'
      };

      // 模拟数据库更新
      (db as any).mockImplementation(() => ({
        where: jest.fn().mockReturnThis(),
        update: jest.fn().mockResolvedValue(1),
      }) as any);

      // 模拟财务服务
      mockFinanceService.recordTransaction = jest.fn().mockResolvedValue(undefined);

      const result = await PaymentService.handleCallback(callback);

      expect(result).toBe(true);
    });

    it('should handle failed payment callback', async () => {
      const callback: PaymentCallback = {
        paymentId: 'pay-1',
        orderId: 'order-1',
        status: 'FAILED',
        transactionId: 'trans-1',
        amount: 100,
        currency: 'USD',
        timestamp: new Date(),
        signature: 'signature'
      };

      // 模拟数据库更新
      (db as any).mockImplementation(() => ({
        where: jest.fn().mockReturnThis(),
        update: jest.fn().mockResolvedValue(1),
      }) as any);

      const result = await PaymentService.handleCallback(callback);

      expect(result).toBe(true);
    });

    it('should handle callback failure', async () => {
      const callback: PaymentCallback = {
        paymentId: 'pay-1',
        orderId: 'order-1',
        status: 'SUCCESS',
        transactionId: 'trans-1',
        amount: 100,
        currency: 'USD',
        timestamp: new Date(),
        signature: 'signature'
      };

      // 模拟数据库错误
      (db as any).mockImplementation(() => ({
        where: jest.fn().mockReturnThis(),
        update: jest.fn().mockRejectedValue(new Error('Database error')),
      }) as any);

      const result = await PaymentService.handleCallback(callback);

      expect(result).toBe(false);
    });
  });

  describe('processRefund', () => {
    it('should process refund successfully', async () => {
      const refundRequest: RefundRequest = {
        tenantId: 'tenant-1',
        paymentId: 'pay-1',
        orderId: 'order-1',
        amount: 50,
        reason: 'Product return'
      };

      // 模拟数据库插入
      (db as any).mockImplementation(() => ({
        insert: jest.fn().mockResolvedValue([1]),
      }) as any);

      const result = await PaymentService.processRefund(refundRequest);

      expect(result).toMatch(/REFUND-\d+-\d+/);
    });

    it('should handle refund failure', async () => {
      const refundRequest: RefundRequest = {
        tenantId: 'tenant-1',
        paymentId: 'pay-1',
        orderId: 'order-1',
        amount: 50,
        reason: 'Product return'
      };

      // 模拟数据库错误
      (db as any).mockImplementation(() => ({
        insert: jest.fn().mockRejectedValue(new Error('Database error')),
      }) as any);

      await expect(PaymentService.processRefund(refundRequest)).rejects.toThrow('Failed to initiate refund: Database error');
    });
  });

  describe('getPaymentStatus', () => {
    it('should return payment status', async () => {
      const paymentId = 'pay-1';

      // 模拟数据库查询
      (db as any).mockImplementation(() => ({
        where: jest.fn().mockReturnThis(),
        first: jest.fn().mockResolvedValue({ status: 'SUCCESS' }),
      }) as any);

      const status = await PaymentService.getPaymentStatus(paymentId);

      expect(status).toBe('SUCCESS');
    });

    it('should return NOT_FOUND for non-existent payment', async () => {
      const paymentId = 'pay-999';

      // 模拟数据库查询返回空
      (db as any).mockImplementation(() => ({
        where: jest.fn().mockReturnThis(),
        first: jest.fn().mockResolvedValue(null),
      }) as any);

      const status = await PaymentService.getPaymentStatus(paymentId);

      expect(status).toBe('NOT_FOUND');
    });
  });

  describe('reconcilePayments', () => {
    it('should reconcile payments successfully', async () => {
      const tenantId = 'tenant-1';
      const date = new Date();

      // 模拟支付记录查询
      (db as any).mockImplementation((table: string) => {
        if (table === 'cf_payments') {
          return {
            where: jest.fn().mockReturnThis(),
            then: jest.fn().mockResolvedValue([
              { status: 'SUCCESS', amount: 100 },
              { status: 'PENDING', amount: 50 },
              { status: 'FAILED', amount: 25 }
            ]),
          } as any;
        } else if (table === 'cf_refunds') {
          return {
            where: jest.fn().mockReturnThis(),
            then: jest.fn().mockResolvedValue([
              { status: 'SUCCESS', amount: 20 }
            ]),
          } as any;
        }
        return {} as any;
      });

      const result = await PaymentService.reconcilePayments(tenantId, date);

      expect(result.date).toBe(date.toISOString().split('T')[0]);
      expect(result.payments.total).toBe(3);
      expect(result.payments.successful).toBe(1);
      expect(result.payments.pending).toBe(1);
      expect(result.payments.failed).toBe(1);
      expect(result.payments.totalAmount).toBe(175);
      expect(result.refunds.total).toBe(1);
      expect(result.refunds.successful).toBe(1);
      expect(result.refunds.totalAmount).toBe(20);
      expect(result.netAmount).toBe(155);
    });
  });

  describe('batchProcessPayments', () => {
    it('should batch process payments successfully', async () => {
      const tenantId = 'tenant-1';
      const paymentIds = ['pay-1', 'pay-2', 'pay-3'];

      // 模拟支付状态查询
      (db as any).mockImplementation(() => ({
        where: jest.fn().mockReturnThis(),
        first: jest.fn().mockResolvedValue({ status: 'SUCCESS' }),
      }) as any);

      const result = await PaymentService.batchProcessPayments(tenantId, paymentIds, 'SYNC_STATUS');

      expect(result.success).toBe(3);
      expect(result.failed).toBe(0);
    });
  });

  describe('getPayments', () => {
    it('should get payments with pagination', async () => {
      const tenantId = 'tenant-1';
      const params = {
        page: 1,
        pageSize: 10
      };

      // 模拟数据库查询
      (db as any).mockImplementation(() => ({
        where: jest.fn().mockReturnThis(),
        count: jest.fn().mockReturnThis(),
        first: jest.fn().mockResolvedValue({ count: 25 }),
        offset: jest.fn().mockReturnThis(),
        limit: jest.fn().mockReturnThis(),
        orderBy: jest.fn().mockResolvedValue([
          { id: 'pay-1', status: 'SUCCESS', amount: 100 },
          { id: 'pay-2', status: 'PENDING', amount: 50 }
        ]),
      }) as any);

      const result = await PaymentService.getPayments(tenantId, params);

      expect(result.payments).toHaveLength(2);
      expect(result.total).toBe(25);
    });
  });

  describe('getPaymentStats', () => {
    it('should get payment stats', async () => {
      const tenantId = 'tenant-1';
      const params = {
        startDate: '2026-01-01',
        endDate: '2026-01-31'
      };

      // 模拟数据库查询
      (db as any).mockImplementation((table: string) => {
        if (table === 'cf_payments') {
          return {
            where: jest.fn().mockReturnThis(),
            sum: jest.fn().mockReturnThis(),
            first: jest.fn().mockResolvedValue({ total: 1000 }),
            select: jest.fn().mockReturnThis(),
            count: jest.fn().mockReturnThis(),
            groupBy: jest.fn().mockResolvedValue([
              { status: 'SUCCESS', count: 10, amount: 800 },
              { status: 'FAILED', count: 2, amount: 200 }
            ]),
          } as any;
        } else if (table === 'cf_refunds') {
          return {
            where: jest.fn().mockReturnThis(),
            sum: jest.fn().mockReturnThis(),
            first: jest.fn().mockResolvedValue({ total: 100 }),
          } as any;
        }
        return {} as any;
      });

      const result = await PaymentService.getPaymentStats(tenantId, params);

      expect(result.totalAmount).toBe(1000);
      expect(result.completedAmount).toBe(1000); // 因为我们只模拟了总金额
      expect(result.refundedAmount).toBe(100);
    });
  });

  describe('generateReconciliationReport', () => {
    it('should generate reconciliation report', async () => {
      const tenantId = 'tenant-1';
      const params = {
        startDate: '2026-01-01',
        endDate: '2026-01-02'
      };

      // 模拟对账结果
      const mockReconcilePayments = jest.spyOn(PaymentService, 'reconcilePayments');
      mockReconcilePayments.mockResolvedValue({
        date: '2026-01-01',
        payments: {
          total: 2,
          successful: 1,
          pending: 1,
          failed: 0,
          totalAmount: 150
        },
        refunds: {
          total: 1,
          successful: 1,
          totalAmount: 50
        },
        netAmount: 100
      });

      const result = await PaymentService.generateReconciliationReport(tenantId, params);

      expect(result.tenantId).toBe(tenantId);
      expect(result.period.startDate).toBe(params.startDate);
      expect(result.period.endDate).toBe(params.endDate);
      expect(result.dailyResults).toHaveLength(2); // 2天
    });
  });

  describe('handleReconciliationDiscrepancy', () => {
    it('should handle reconciliation discrepancy', async () => {
      const tenantId = 'tenant-1';
      const discrepancy = {
        date: '2026-01-01',
        expectedAmount: 1000,
        actualAmount: 950,
        paymentMethod: 'ALIPAY',
        reason: 'Missing transaction'
      };

      const result = await PaymentService.handleReconciliationDiscrepancy(tenantId, discrepancy);

      expect(result).toBe(true);
    });
  });
});
