import { Test, TestingModule } from '@nestjs/testing';
import { RewardApplicationService } from '../../src/application/services/reward-application.service';
import { RewardCalculationService } from '../../src/domain/services/reward-calculation.service';
import { RewardExpirationService } from '../../src/domain/services/reward-expiration.service';
import { REWARD_LEDGER_ENTRY_REPOSITORY } from '../../src/domain/repositories/reward-ledger-entry.repository.interface';
import { REWARD_SUMMARY_REPOSITORY } from '../../src/domain/repositories/reward-summary.repository.interface';
import { EventPublisherService } from '../../src/infrastructure/kafka/event-publisher.service';
import { WalletServiceClient } from '../../src/infrastructure/external/wallet-service/wallet-service.client';
import { RewardLedgerEntry } from '../../src/domain/aggregates/reward-ledger-entry/reward-ledger-entry.aggregate';
import { RewardSummary } from '../../src/domain/aggregates/reward-summary/reward-summary.aggregate';
import { RewardSource } from '../../src/domain/value-objects/reward-source.vo';
import { RightType } from '../../src/domain/value-objects/right-type.enum';
import { RewardStatus } from '../../src/domain/value-objects/reward-status.enum';
import { Money } from '../../src/domain/value-objects/money.vo';
import { Hashpower } from '../../src/domain/value-objects/hashpower.vo';

describe('RewardApplicationService (Integration)', () => {
  let service: RewardApplicationService;
  let mockLedgerRepository: any;
  let mockSummaryRepository: any;
  let mockEventPublisher: any;
  let mockWalletService: any;
  let mockCalculationService: any;

  beforeEach(async () => {
    mockLedgerRepository = {
      save: jest.fn(),
      saveAll: jest.fn(),
      findPendingByUserId: jest.fn(),
      findSettleableByUserId: jest.fn(),
      findExpiredPending: jest.fn(),
      findByUserId: jest.fn(),
      countByUserId: jest.fn(),
    };

    mockSummaryRepository = {
      getOrCreate: jest.fn(),
      findByUserId: jest.fn(),
      save: jest.fn(),
    };

    mockEventPublisher = {
      publishAll: jest.fn(),
    };

    mockWalletService = {
      executeSwap: jest.fn(),
    };

    mockCalculationService = {
      calculateRewards: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        RewardApplicationService,
        {
          provide: RewardCalculationService,
          useValue: mockCalculationService,
        },
        {
          provide: RewardExpirationService,
          useValue: { expireOverdueRewards: jest.fn((r) => r) },
        },
        {
          provide: REWARD_LEDGER_ENTRY_REPOSITORY,
          useValue: mockLedgerRepository,
        },
        {
          provide: REWARD_SUMMARY_REPOSITORY,
          useValue: mockSummaryRepository,
        },
        {
          provide: EventPublisherService,
          useValue: mockEventPublisher,
        },
        {
          provide: WalletServiceClient,
          useValue: mockWalletService,
        },
      ],
    }).compile();

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

  describe('distributeRewards', () => {
    it('should distribute rewards and update summaries', async () => {
      const params = {
        sourceOrderNo: 'ORDER001',
        sourceUserId: BigInt(100),
        treeCount: 10,
        provinceCode: '440000',
        cityCode: '440100',
      };

      const mockReward = RewardLedgerEntry.createPending({
        userId: BigInt(200),
        accountSequence: 'D2412190100',
        rewardSource: RewardSource.create(RightType.SHARE_RIGHT, 'ORDER001', BigInt(100)),
        usdtAmount: Money.USDT(500),
        hashpowerAmount: Hashpower.zero(),
        memo: 'Test',
      });

      const mockSummary = RewardSummary.create(BigInt(200), 'D2412190100');

      mockCalculationService.calculateRewards.mockResolvedValue([mockReward]);
      mockSummaryRepository.getOrCreate.mockResolvedValue(mockSummary);

      await service.distributeRewards(params);

      expect(mockCalculationService.calculateRewards).toHaveBeenCalledWith(params);
      expect(mockLedgerRepository.saveAll).toHaveBeenCalledWith([mockReward]);
      expect(mockSummaryRepository.save).toHaveBeenCalled();
      expect(mockEventPublisher.publishAll).toHaveBeenCalled();
    });
  });

  describe('claimPendingRewardsForUser', () => {
    it('should claim pending rewards and move to settleable', async () => {
      const userId = BigInt(100);

      const pendingReward = RewardLedgerEntry.createPending({
        userId,
        accountSequence: 'D2412190100',
        rewardSource: RewardSource.create(RightType.SHARE_RIGHT, 'ORDER001', BigInt(50)),
        usdtAmount: Money.USDT(500),
        hashpowerAmount: Hashpower.zero(),
        memo: 'Test reward',
      });

      const mockSummary = RewardSummary.create(userId, 'D2412190100');
      mockSummary.addPending(
        Money.USDT(500),
        Hashpower.zero(),
        new Date(Date.now() + 24 * 60 * 60 * 1000),
      );

      mockLedgerRepository.findPendingByUserId.mockResolvedValue([pendingReward]);
      mockSummaryRepository.getOrCreate.mockResolvedValue(mockSummary);

      const result = await service.claimPendingRewardsForUser(userId);

      expect(result.claimedCount).toBe(1);
      expect(result.totalUsdtClaimed).toBe(500);
      expect(mockLedgerRepository.save).toHaveBeenCalled();
      expect(mockSummaryRepository.save).toHaveBeenCalled();
    });

    it('should skip expired rewards', async () => {
      const userId = BigInt(100);

      const expiredReward = RewardLedgerEntry.reconstitute({
        id: BigInt(1),
        userId,
        accountSequence: 'D2412190100',
        rewardSource: RewardSource.create(RightType.SHARE_RIGHT, 'ORDER001', BigInt(50)),
        usdtAmount: 500,
        hashpowerAmount: 0,
        rewardStatus: RewardStatus.PENDING,
        createdAt: new Date(Date.now() - 25 * 60 * 60 * 1000),
        expireAt: new Date(Date.now() - 1000),
        claimedAt: null,
        settledAt: null,
        expiredAt: null,
        memo: 'Expired reward',
      });

      const mockSummary = RewardSummary.create(userId, 'D2412190100');

      mockLedgerRepository.findPendingByUserId.mockResolvedValue([expiredReward]);
      mockSummaryRepository.getOrCreate.mockResolvedValue(mockSummary);

      const result = await service.claimPendingRewardsForUser(userId);

      expect(result.claimedCount).toBe(0);
      expect(result.totalUsdtClaimed).toBe(0);
    });
  });

  describe('settleRewards', () => {
    it('should settle rewards and call wallet service', async () => {
      const accountSequence = 'D2412190100';

      const settleableReward = RewardLedgerEntry.reconstitute({
        id: BigInt(1),
        userId: BigInt(100),
        accountSequence: 'D2412190100',
        rewardSource: RewardSource.create(RightType.SHARE_RIGHT, 'ORDER001', BigInt(50)),
        usdtAmount: 500,
        hashpowerAmount: 0,
        rewardStatus: RewardStatus.SETTLEABLE,
        createdAt: new Date(),
        expireAt: null,
        claimedAt: new Date(),
        settledAt: null,
        expiredAt: null,
        memo: 'Test',
      });

      const mockSummary = RewardSummary.create(BigInt(100), 'D2412190100');
      mockSummary.addSettleable(Money.USDT(500), Hashpower.zero());

      mockLedgerRepository.findSettleableByUserId.mockResolvedValue([settleableReward]);
      mockSummaryRepository.getOrCreate.mockResolvedValue(mockSummary);
      mockWalletService.executeSwap.mockResolvedValue({
        success: true,
        receivedAmount: 0.25,
        txHash: '0x123',
      });

      const result = await service.settleRewards({
        accountSequence,
        settleCurrency: 'BNB',
      });

      expect(result.success).toBe(true);
      expect(result.settledUsdtAmount).toBe(500);
      expect(result.receivedAmount).toBe(0.25);
      expect(result.settleCurrency).toBe('BNB');
      expect(mockWalletService.executeSwap).toHaveBeenCalledWith({
        userId: BigInt(100),
        usdtAmount: 500,
        targetCurrency: 'BNB',
      });
    });

    it('should return error when no settleable rewards', async () => {
      const accountSequence = 'D2412190100';

      mockLedgerRepository.findSettleableByUserId.mockResolvedValue([]);

      const result = await service.settleRewards({
        accountSequence,
        settleCurrency: 'BNB',
      });

      expect(result.success).toBe(false);
      expect(result.error).toBe('没有可结算的收益');
    });

    it('should handle wallet service failure', async () => {
      const accountSequence = 'D2412190100';

      const settleableReward = RewardLedgerEntry.reconstitute({
        id: BigInt(1),
        userId: BigInt(100),
        accountSequence: 'D2412190100',
        rewardSource: RewardSource.create(RightType.SHARE_RIGHT, 'ORDER001', BigInt(50)),
        usdtAmount: 500,
        hashpowerAmount: 0,
        rewardStatus: RewardStatus.SETTLEABLE,
        createdAt: new Date(),
        expireAt: null,
        claimedAt: new Date(),
        settledAt: null,
        expiredAt: null,
        memo: 'Test',
      });

      const mockSummary = RewardSummary.create(BigInt(100), 'D2412190100');
      mockSummary.addSettleable(Money.USDT(500), Hashpower.zero());

      mockLedgerRepository.findSettleableByUserId.mockResolvedValue([settleableReward]);
      mockSummaryRepository.getOrCreate.mockResolvedValue(mockSummary);
      mockWalletService.executeSwap.mockResolvedValue({
        success: false,
        error: 'Insufficient liquidity',
      });

      const result = await service.settleRewards({
        accountSequence,
        settleCurrency: 'BNB',
      });

      expect(result.success).toBe(false);
      expect(result.error).toBe('Insufficient liquidity');
    });
  });

  describe('getRewardSummary', () => {
    it('should return reward summary for user', async () => {
      const accountSequence = 'D2412190100';
      const mockSummary = RewardSummary.create(BigInt(100), accountSequence);

      mockSummaryRepository.findByAccountSequence.mockResolvedValue(mockSummary);

      const result = await service.getRewardSummary(accountSequence);

      expect(result.pendingUsdt).toBe(0);
      expect(result.settleableUsdt).toBe(0);
      expect(result.settledTotalUsdt).toBe(0);
      expect(result.expiredTotalUsdt).toBe(0);
    });

    it('should return zero values when no summary exists', async () => {
      const accountSequence = 'D2412190100';

      mockSummaryRepository.findByAccountSequence.mockResolvedValue(null);

      const result = await service.getRewardSummary(accountSequence);

      expect(result.pendingUsdt).toBe(0);
      expect(result.settleableUsdt).toBe(0);
    });
  });

  describe('getRewardDetails', () => {
    it('should return paginated reward details', async () => {
      const accountSequence = 'D2412190100';
      const mockReward = RewardLedgerEntry.reconstitute({
        id: BigInt(1),
        userId: BigInt(100),
        accountSequence,
        rewardSource: RewardSource.create(RightType.SHARE_RIGHT, 'ORDER001', BigInt(50)),
        usdtAmount: 500,
        hashpowerAmount: 0,
        rewardStatus: RewardStatus.PENDING,
        createdAt: new Date(),
        expireAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
        claimedAt: null,
        settledAt: null,
        expiredAt: null,
        memo: 'Test',
      });

      mockLedgerRepository.findByAccountSequence.mockResolvedValue([mockReward]);
      mockLedgerRepository.countByAccountSequence.mockResolvedValue(1);

      const result = await service.getRewardDetails(accountSequence, {}, { page: 1, pageSize: 20 });

      expect(result.data).toHaveLength(1);
      expect(result.data[0].usdtAmount).toBe(500);
      expect(result.pagination.total).toBe(1);
    });
  });

  describe('getPendingRewards', () => {
    it('should return pending rewards with countdown', async () => {
      const accountSequence = 'D2412190100';

      const pendingReward = RewardLedgerEntry.createPending({
        userId: BigInt(100),
        accountSequence,
        rewardSource: RewardSource.create(RightType.SHARE_RIGHT, 'ORDER001', BigInt(50)),
        usdtAmount: Money.USDT(500),
        hashpowerAmount: Hashpower.zero(),
        memo: 'Test',
      });

      mockLedgerRepository.findPendingByAccountSequence.mockResolvedValue([pendingReward]);

      const result = await service.getPendingRewards(accountSequence);

      expect(result).toHaveLength(1);
      expect(result[0].usdtAmount).toBe(500);
      expect(result[0].remainingTimeMs).toBeGreaterThan(0);
    });
  });
});
