import { describe, it, expect, beforeEach, vi } from 'vitest';
import { DataSource, Repository } from 'typeorm';
import { CreditBalanceService } from '../../src/services';
import { CreditBalanceMt, CreditBalanceLogMt, CreditBalanceChangeType } from '../../src/entities';

describe('CreditBalanceService', () => {
  let service: CreditBalanceService;
  let mockDataSource: DataSource;
  let mockCreditBalanceRepository: Repository<CreditBalanceMt>;
  let mockCreditBalanceLogRepository: Repository<CreditBalanceLogMt>;

  beforeEach(() => {
    mockCreditBalanceRepository = {
      findOne: vi.fn(),
      save: vi.fn(),
      create: vi.fn(),
      findAndCount: vi.fn()
    } as any;

    mockCreditBalanceLogRepository = {
      save: vi.fn(),
      create: vi.fn(),
      findAndCount: vi.fn()
    } as any;

    mockDataSource = {
      getRepository: vi.fn((entity) => {
        if (entity === CreditBalanceMt) {
          return mockCreditBalanceRepository;
        }
        if (entity === CreditBalanceLogMt) {
          return mockCreditBalanceLogRepository;
        }
        return {} as any;
      }),
      transaction: vi.fn(async (callback) => {
        const mockManager = {
          getRepository: vi.fn((entity) => {
            if (entity === CreditBalanceMt) {
              return mockCreditBalanceRepository;
            }
            if (entity === CreditBalanceLogMt) {
              return mockCreditBalanceLogRepository;
            }
            return {} as any;
          })
        };
        return await callback(mockManager);
      })
    } as any;

    service = new CreditBalanceService(mockDataSource);
  });

  describe('setLimit', () => {
    it('应该创建新的信用额度记录', async () => {
      const params = {
        tenantId: 1,
        userId: 1001,
        totalLimit: 10000,
        operatorId: 1,
        remark: '初始化额度'
      };

      const mockCreditBalance = {
        id: 1,
        tenantId: 1,
        userId: 1001,
        totalLimit: 10000,
        usedAmount: 0,
        availableAmount: 10000,
        isEnabled: 1,
        createdAt: new Date(),
        updatedAt: new Date()
      };

      vi.mocked(mockCreditBalanceRepository.findOne).mockResolvedValue(null);
      vi.mocked(mockCreditBalanceRepository.create).mockReturnValue(mockCreditBalance);
      vi.mocked(mockCreditBalanceRepository.save).mockResolvedValue(mockCreditBalance);
      vi.mocked(mockCreditBalanceLogRepository.create).mockReturnValue({} as any);
      vi.mocked(mockCreditBalanceLogRepository.save).mockResolvedValue({} as any);

      const result = await service.setLimit(params);

      expect(result).toEqual(mockCreditBalance);
      expect(mockCreditBalanceRepository.findOne).toHaveBeenCalledWith({
        where: { tenantId: 1, userId: 1001 }
      });
      expect(mockCreditBalanceRepository.create).toHaveBeenCalledWith({
        tenantId: 1,
        userId: 1001,
        totalLimit: 10000,
        usedAmount: 0,
        isEnabled: 1
      });
    });

    it('应该更新现有的信用额度记录', async () => {
      const params = {
        tenantId: 1,
        userId: 1001,
        totalLimit: 15000,
        operatorId: 1,
        remark: '提升额度'
      };

      const existingBalance = {
        id: 1,
        tenantId: 1,
        userId: 1001,
        totalLimit: 10000,
        usedAmount: 500,
        availableAmount: 9500,
        isEnabled: 1,
        createdAt: new Date(),
        updatedAt: new Date()
      };

      const updatedBalance = {
        ...existingBalance,
        totalLimit: 15000,
        updatedAt: new Date()
      };

      vi.mocked(mockCreditBalanceRepository.findOne).mockResolvedValue(existingBalance);
      vi.mocked(mockCreditBalanceRepository.save).mockResolvedValue(updatedBalance);
      vi.mocked(mockCreditBalanceLogRepository.create).mockReturnValue({} as any);
      vi.mocked(mockCreditBalanceLogRepository.save).mockResolvedValue({} as any);

      const result = await service.setLimit(params);

      expect(result.totalLimit).toBe(15000);
      expect(mockCreditBalanceRepository.save).toHaveBeenCalledWith(updatedBalance);
    });
  });

  describe('adjustLimit', () => {
    it('应该成功增加额度', async () => {
      const params = {
        tenantId: 1,
        userId: 1001,
        adjustAmount: 5000,
        operatorId: 1,
        remark: '增加额度'
      };

      const existingBalance = {
        id: 1,
        tenantId: 1,
        userId: 1001,
        totalLimit: 10000,
        usedAmount: 500,
        availableAmount: 9500,
        isEnabled: 1,
        createdAt: new Date(),
        updatedAt: new Date()
      };

      const updatedBalance = {
        ...existingBalance,
        totalLimit: 15000,
        updatedAt: new Date()
      };

      vi.mocked(mockCreditBalanceRepository.findOne).mockResolvedValue(existingBalance);
      vi.mocked(mockCreditBalanceRepository.save).mockResolvedValue(updatedBalance);
      vi.mocked(mockCreditBalanceLogRepository.create).mockReturnValue({} as any);
      vi.mocked(mockCreditBalanceLogRepository.save).mockResolvedValue({} as any);

      const result = await service.adjustLimit(params);

      expect(result.totalLimit).toBe(15000);
    });

    it('应该成功减少额度', async () => {
      const params = {
        tenantId: 1,
        userId: 1001,
        adjustAmount: -2000,
        operatorId: 1,
        remark: '减少额度'
      };

      const existingBalance = {
        id: 1,
        tenantId: 1,
        userId: 1001,
        totalLimit: 10000,
        usedAmount: 500,
        availableAmount: 9500,
        isEnabled: 1,
        createdAt: new Date(),
        updatedAt: new Date()
      };

      const updatedBalance = {
        ...existingBalance,
        totalLimit: 8000,
        updatedAt: new Date()
      };

      vi.mocked(mockCreditBalanceRepository.findOne).mockResolvedValue(existingBalance);
      vi.mocked(mockCreditBalanceRepository.save).mockResolvedValue(updatedBalance);
      vi.mocked(mockCreditBalanceLogRepository.create).mockReturnValue({} as any);
      vi.mocked(mockCreditBalanceLogRepository.save).mockResolvedValue({} as any);

      const result = await service.adjustLimit(params);

      expect(result.totalLimit).toBe(8000);
    });

    it('当调整金额为0时应该抛出错误', async () => {
      const params = {
        tenantId: 1,
        userId: 1001,
        adjustAmount: 0,
        operatorId: 1,
        remark: '无效调整'
      };

      await expect(service.adjustLimit(params)).rejects.toThrow('调整金额不能为0');
    });

    it('当调整后的额度小于已用额度时应该抛出错误', async () => {
      const params = {
        tenantId: 1,
        userId: 1001,
        adjustAmount: -8000,
        operatorId: 1,
        remark: '过度减少额度'
      };

      const existingBalance = {
        id: 1,
        tenantId: 1,
        userId: 1001,
        totalLimit: 10000,
        usedAmount: 5000,
        availableAmount: 5000,
        isEnabled: 1,
        createdAt: new Date(),
        updatedAt: new Date()
      };

      vi.mocked(mockCreditBalanceRepository.findOne).mockResolvedValue(existingBalance);

      await expect(service.adjustLimit(params)).rejects.toThrow('调整后的总额度不能小于已用额度');
    });
  });

  describe('deductAmount', () => {
    it('应该成功扣减额度', async () => {
      const params = {
        tenantId: 1,
        userId: 1001,
        amount: 500,
        referenceId: 'ORD202412010001',
        operatorId: 1,
        remark: '订单支付'
      };

      const existingBalance = {
        id: 1,
        tenantId: 1,
        userId: 1001,
        totalLimit: 10000,
        usedAmount: 0,
        availableAmount: 10000,
        isEnabled: 1,
        createdAt: new Date(),
        updatedAt: new Date()
      };

      const updatedBalance = {
        ...existingBalance,
        usedAmount: 500,
        updatedAt: new Date()
      };

      vi.mocked(mockCreditBalanceRepository.findOne).mockResolvedValue(existingBalance);
      vi.mocked(mockCreditBalanceRepository.save).mockResolvedValue(updatedBalance);
      vi.mocked(mockCreditBalanceLogRepository.create).mockReturnValue({} as any);
      vi.mocked(mockCreditBalanceLogRepository.save).mockResolvedValue({} as any);

      const result = await service.deductAmount(params);

      expect(result.usedAmount).toBe(500);
    });

    it('当额度不足时应该抛出错误', async () => {
      const params = {
        tenantId: 1,
        userId: 1001,
        amount: 15000,
        referenceId: 'ORD202412010001',
        operatorId: 1,
        remark: '大额订单支付'
      };

      const existingBalance = {
        id: 1,
        tenantId: 1,
        userId: 1001,
        totalLimit: 10000,
        usedAmount: 0,
        availableAmount: 10000,
        isEnabled: 1,
        createdAt: new Date(),
        updatedAt: new Date()
      };

      vi.mocked(mockCreditBalanceRepository.findOne).mockResolvedValue(existingBalance);

      await expect(service.deductAmount(params)).rejects.toThrow('信用额度不足');
    });

    it('当额度已禁用时应该抛出错误', async () => {
      const params = {
        tenantId: 1,
        userId: 1001,
        amount: 500,
        referenceId: 'ORD202412010001',
        operatorId: 1,
        remark: '订单支付'
      };

      const existingBalance = {
        id: 1,
        tenantId: 1,
        userId: 1001,
        totalLimit: 10000,
        usedAmount: 0,
        availableAmount: 10000,
        isEnabled: 0,
        createdAt: new Date(),
        updatedAt: new Date()
      };

      vi.mocked(mockCreditBalanceRepository.findOne).mockResolvedValue(existingBalance);

      await expect(service.deductAmount(params)).rejects.toThrow('用户信用额度已禁用');
    });
  });

  describe('restoreAmount', () => {
    it('应该成功恢复额度', async () => {
      const params = {
        tenantId: 1,
        userId: 1001,
        amount: 500,
        changeType: CreditBalanceChangeType.CANCEL_ORDER,
        referenceId: 'ORD202412010001',
        operatorId: 1,
        remark: '取消订单恢复额度'
      };

      const existingBalance = {
        id: 1,
        tenantId: 1,
        userId: 1001,
        totalLimit: 10000,
        usedAmount: 2000,
        availableAmount: 8000,
        isEnabled: 1,
        createdAt: new Date(),
        updatedAt: new Date()
      };

      const updatedBalance = {
        ...existingBalance,
        usedAmount: 1500,
        updatedAt: new Date()
      };

      vi.mocked(mockCreditBalanceRepository.findOne).mockResolvedValue(existingBalance);
      vi.mocked(mockCreditBalanceRepository.save).mockResolvedValue(updatedBalance);
      vi.mocked(mockCreditBalanceLogRepository.create).mockReturnValue({} as any);
      vi.mocked(mockCreditBalanceLogRepository.save).mockResolvedValue({} as any);

      const result = await service.restoreAmount(params);

      expect(result.usedAmount).toBe(1500);
    });

    it('当恢复金额超过已用额度时应该抛出错误', async () => {
      const params = {
        tenantId: 1,
        userId: 1001,
        amount: 3000,
        changeType: CreditBalanceChangeType.REFUND,
        referenceId: 'ORD202412010001',
        operatorId: 1,
        remark: '退款恢复额度'
      };

      const existingBalance = {
        id: 1,
        tenantId: 1,
        userId: 1001,
        totalLimit: 10000,
        usedAmount: 2000,
        availableAmount: 8000,
        isEnabled: 1,
        createdAt: new Date(),
        updatedAt: new Date()
      };

      vi.mocked(mockCreditBalanceRepository.findOne).mockResolvedValue(existingBalance);

      await expect(service.restoreAmount(params)).rejects.toThrow('恢复金额不能超过已用额度');
    });
  });

  describe('getBalance', () => {
    it('应该返回用户信用额度', async () => {
      const tenantId = 1;
      const userId = 1001;

      const mockBalance = {
        id: 1,
        tenantId: 1,
        userId: 1001,
        totalLimit: 10000,
        usedAmount: 500,
        availableAmount: 9500,
        isEnabled: 1,
        createdAt: new Date(),
        updatedAt: new Date()
      };

      vi.mocked(mockCreditBalanceRepository.findOne).mockResolvedValue(mockBalance);

      const result = await service.getBalance(tenantId, userId);

      expect(result).toEqual(mockBalance);
      expect(mockCreditBalanceRepository.findOne).toHaveBeenCalledWith({
        where: { tenantId, userId }
      });
    });

    it('当用户没有信用额度时应该返回null', async () => {
      const tenantId = 1;
      const userId = 9999;

      vi.mocked(mockCreditBalanceRepository.findOne).mockResolvedValue(null);

      const result = await service.getBalance(tenantId, userId);

      expect(result).toBeNull();
    });
  });
});