import { Test, TestingModule } from '@nestjs/testing';
import { WalletApplicationService } from './wallet-application.service';
import {
  WALLET_ACCOUNT_REPOSITORY,
  LEDGER_ENTRY_REPOSITORY,
  DEPOSIT_ORDER_REPOSITORY,
  SETTLEMENT_ORDER_REPOSITORY,
} from '@/domain/repositories';
import { WalletAccount, DepositOrder, SettlementOrder } from '@/domain/aggregates';
import { UserId, ChainType, SettleCurrency, Money, Hashpower, WalletStatus } from '@/domain/value-objects';
import {
  HandleDepositCommand,
  DeductForPlantingCommand,
  AddRewardsCommand,
  ClaimRewardsCommand,
  SettleRewardsCommand,
} from '@/application/commands';
import { GetMyWalletQuery, GetMyLedgerQuery } from '@/application/queries';
import Decimal from 'decimal.js';

describe('WalletApplicationService', () => {
  let service: WalletApplicationService;
  let mockWalletRepo: any;
  let mockLedgerRepo: any;
  let mockDepositRepo: any;
  let mockSettlementRepo: any;

  const createMockWallet = (userId: bigint, usdtBalance = 0) => {
    return WalletAccount.reconstruct({
      walletId: BigInt(1),
      accountSequence: `D${userId.toString().padStart(11, '0')}`, // 生成 accountSequence
      userId,
      usdtAvailable: new Decimal(usdtBalance),
      usdtFrozen: new Decimal(0),
      dstAvailable: new Decimal(0),
      dstFrozen: new Decimal(0),
      bnbAvailable: new Decimal(0),
      bnbFrozen: new Decimal(0),
      ogAvailable: new Decimal(0),
      ogFrozen: new Decimal(0),
      rwadAvailable: new Decimal(0),
      rwadFrozen: new Decimal(0),
      hashpower: new Decimal(0),
      pendingUsdt: new Decimal(0),
      pendingHashpower: new Decimal(0),
      pendingExpireAt: null,
      settleableUsdt: new Decimal(0),
      settleableHashpower: new Decimal(0),
      settledTotalUsdt: new Decimal(0),
      settledTotalHashpower: new Decimal(0),
      expiredTotalUsdt: new Decimal(0),
      expiredTotalHashpower: new Decimal(0),
      status: 'ACTIVE',
      hasPlanted: false,
      version: 0,
      createdAt: new Date(),
      updatedAt: new Date(),
    });
  };

  beforeEach(async () => {
    mockWalletRepo = {
      save: jest.fn(),
      findById: jest.fn(),
      findByUserId: jest.fn(),
      getOrCreate: jest.fn(),
      findByUserIds: jest.fn(),
    };

    mockLedgerRepo = {
      save: jest.fn(),
      saveAll: jest.fn(),
      findByUserId: jest.fn(),
      findByAccountSequence: jest.fn(),
      findByRefOrderId: jest.fn(),
      findByRefTxHash: jest.fn(),
    };

    mockDepositRepo = {
      save: jest.fn(),
      findById: jest.fn(),
      findByTxHash: jest.fn(),
      findByUserId: jest.fn(),
      existsByTxHash: jest.fn(),
    };

    mockSettlementRepo = {
      save: jest.fn(),
      findById: jest.fn(),
      findByUserId: jest.fn(),
      findPendingOrders: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        WalletApplicationService,
        { provide: WALLET_ACCOUNT_REPOSITORY, useValue: mockWalletRepo },
        { provide: LEDGER_ENTRY_REPOSITORY, useValue: mockLedgerRepo },
        { provide: DEPOSIT_ORDER_REPOSITORY, useValue: mockDepositRepo },
        { provide: SETTLEMENT_ORDER_REPOSITORY, useValue: mockSettlementRepo },
      ],
    }).compile();

    service = module.get<WalletApplicationService>(WalletApplicationService);
  });

  describe('handleDeposit', () => {
    it('should process deposit successfully', async () => {
      const command = new HandleDepositCommand('1', '1', 100, ChainType.KAVA, 'tx_123');
      const mockWallet = createMockWallet(BigInt(1), 0);

      mockDepositRepo.existsByTxHash.mockResolvedValue(false);
      mockWalletRepo.getOrCreate.mockResolvedValue(mockWallet);
      mockDepositRepo.save.mockImplementation((order: DepositOrder) => Promise.resolve(order));
      mockWalletRepo.save.mockImplementation((wallet: WalletAccount) => Promise.resolve(wallet));
      mockLedgerRepo.save.mockResolvedValue({});

      await service.handleDeposit(command);

      expect(mockDepositRepo.existsByTxHash).toHaveBeenCalledWith('tx_123');
      expect(mockWalletRepo.getOrCreate).toHaveBeenCalledWith(BigInt(1));
      expect(mockDepositRepo.save).toHaveBeenCalled();
      expect(mockWalletRepo.save).toHaveBeenCalled();
      expect(mockLedgerRepo.save).toHaveBeenCalled();
    });

    it('should throw error for duplicate transaction', async () => {
      const command = new HandleDepositCommand('1', '1', 100, ChainType.KAVA, 'tx_duplicate');
      mockDepositRepo.existsByTxHash.mockResolvedValue(true);

      await expect(service.handleDeposit(command)).rejects.toThrow('Duplicate');
    });
  });

  describe('deductForPlanting', () => {
    it('should deduct USDT for planting', async () => {
      const command = new DeductForPlantingCommand('1', 50, 'order_123');
      const mockWallet = createMockWallet(BigInt(1), 100);

      mockWalletRepo.findByUserId.mockResolvedValue(mockWallet);
      mockWalletRepo.save.mockImplementation((wallet: WalletAccount) => Promise.resolve(wallet));
      mockLedgerRepo.save.mockResolvedValue({});

      await service.deductForPlanting(command);

      expect(mockWalletRepo.findByUserId).toHaveBeenCalledWith(BigInt(1));
      expect(mockWalletRepo.save).toHaveBeenCalled();
      expect(mockLedgerRepo.save).toHaveBeenCalled();
    });

    it('should throw error when wallet not found', async () => {
      const command = new DeductForPlantingCommand('999', 50, 'order_123');
      mockWalletRepo.findByUserId.mockResolvedValue(null);

      await expect(service.deductForPlanting(command)).rejects.toThrow('not found');
    });
  });

  describe('addRewards', () => {
    it('should add pending rewards', async () => {
      const expireAt = new Date(Date.now() + 86400000);
      const command = new AddRewardsCommand('1', 10, 5, expireAt, 'order_123');
      const mockWallet = createMockWallet(BigInt(1), 100);

      mockWalletRepo.getOrCreate.mockResolvedValue(mockWallet);
      mockWalletRepo.save.mockImplementation((wallet: WalletAccount) => Promise.resolve(wallet));
      mockLedgerRepo.save.mockResolvedValue({});

      await service.addRewards(command);

      expect(mockWalletRepo.getOrCreate).toHaveBeenCalledWith(BigInt(1));
      expect(mockWalletRepo.save).toHaveBeenCalled();
    });
  });

  describe('getMyWallet', () => {
    it('should return wallet DTO', async () => {
      const query = new GetMyWalletQuery('1', '1');
      const mockWallet = createMockWallet(BigInt(1), 100);

      mockWalletRepo.getOrCreate.mockResolvedValue(mockWallet);

      const result = await service.getMyWallet(query);

      expect(result.userId).toBe('1');
      expect(result.balances.usdt.available).toBe(100);
      expect(result.status).toBe('ACTIVE');
    });
  });

  describe('getMyLedger', () => {
    it('should return paginated ledger entries', async () => {
      const query = new GetMyLedgerQuery('D00000000001', 1, 10);

      mockLedgerRepo.findByAccountSequence.mockResolvedValue({
        data: [],
        total: 0,
        page: 1,
        pageSize: 10,
        totalPages: 0,
      });

      const result = await service.getMyLedger(query);

      expect(result.page).toBe(1);
      expect(result.pageSize).toBe(10);
      expect(result.data).toEqual([]);
    });
  });
});
