import { Test, TestingModule } from '@nestjs/testing';
import {
  RewardCalculationService,
  REFERRAL_SERVICE_CLIENT,
  AUTHORIZATION_SERVICE_CLIENT,
} from '../../src/domain/services/reward-calculation.service';
import { RightType } from '../../src/domain/value-objects/right-type.enum';

describe('RewardCalculationService (Integration)', () => {
  let service: RewardCalculationService;
  let mockReferralService: any;
  let mockAuthorizationService: any;

  beforeEach(async () => {
    mockReferralService = {
      getReferralChain: jest.fn(),
    };

    mockAuthorizationService = {
      findNearestAuthorizedProvince: jest.fn(),
      findNearestAuthorizedCity: jest.fn(),
      findNearestCommunity: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        RewardCalculationService,
        {
          provide: REFERRAL_SERVICE_CLIENT,
          useValue: mockReferralService,
        },
        {
          provide: AUTHORIZATION_SERVICE_CLIENT,
          useValue: mockAuthorizationService,
        },
      ],
    }).compile();

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

  describe('calculateRewards', () => {
    const baseParams = {
      sourceOrderNo: 'ORDER001',
      sourceUserId: BigInt(100),
      treeCount: 10,
      provinceCode: '440000',
      cityCode: '440100',
    };

    it('should calculate all 6 types of rewards', async () => {
      // Setup: referrer with planting status
      mockReferralService.getReferralChain.mockResolvedValue({
        ancestors: [{ userId: BigInt(200), hasPlanted: true }],
      });
      mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(BigInt(300));
      mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(BigInt(400));
      mockAuthorizationService.findNearestCommunity.mockResolvedValue(BigInt(500));

      const rewards = await service.calculateRewards(baseParams);

      // Should return 6 rewards (one for each type)
      expect(rewards).toHaveLength(6);

      // Verify each reward type exists
      const rightTypes = rewards.map((r) => r.rewardSource.rightType);
      expect(rightTypes).toContain(RightType.SHARE_RIGHT);
      expect(rightTypes).toContain(RightType.PROVINCE_TEAM_RIGHT);
      expect(rightTypes).toContain(RightType.PROVINCE_AREA_RIGHT);
      expect(rightTypes).toContain(RightType.CITY_TEAM_RIGHT);
      expect(rightTypes).toContain(RightType.CITY_AREA_RIGHT);
      expect(rightTypes).toContain(RightType.COMMUNITY_RIGHT);
    });

    it('should calculate share right reward (500 USDT) when referrer has planted', async () => {
      mockReferralService.getReferralChain.mockResolvedValue({
        ancestors: [{ userId: BigInt(200), hasPlanted: true }],
      });
      mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null);
      mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null);
      mockAuthorizationService.findNearestCommunity.mockResolvedValue(null);

      const rewards = await service.calculateRewards(baseParams);

      const shareReward = rewards.find(
        (r) => r.rewardSource.rightType === RightType.SHARE_RIGHT,
      );
      expect(shareReward).toBeDefined();
      expect(shareReward?.usdtAmount.amount).toBe(500 * 10); // 500 USDT per tree
      expect(shareReward?.userId).toBe(BigInt(200));
      expect(shareReward?.isSettleable).toBe(true); // Already planted, so settleable
    });

    it('should create pending share right reward when referrer has not planted', async () => {
      mockReferralService.getReferralChain.mockResolvedValue({
        ancestors: [{ userId: BigInt(200), hasPlanted: false }],
      });
      mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null);
      mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null);
      mockAuthorizationService.findNearestCommunity.mockResolvedValue(null);

      const rewards = await service.calculateRewards(baseParams);

      const shareReward = rewards.find(
        (r) => r.rewardSource.rightType === RightType.SHARE_RIGHT,
      );
      expect(shareReward).toBeDefined();
      expect(shareReward?.isPending).toBe(true); // Not planted, so pending with 24h countdown
      expect(shareReward?.expireAt).toBeDefined();
    });

    it('should assign share right to headquarters when no referrer', async () => {
      mockReferralService.getReferralChain.mockResolvedValue({
        ancestors: [],
      });
      mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null);
      mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null);
      mockAuthorizationService.findNearestCommunity.mockResolvedValue(null);

      const rewards = await service.calculateRewards(baseParams);

      const shareReward = rewards.find(
        (r) => r.rewardSource.rightType === RightType.SHARE_RIGHT,
      );
      expect(shareReward).toBeDefined();
      expect(shareReward?.userId).toBe(BigInt(1)); // Headquarters community ID
    });

    it('should calculate province team right (20 USDT)', async () => {
      mockReferralService.getReferralChain.mockResolvedValue({ ancestors: [] });
      mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(BigInt(300));
      mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null);
      mockAuthorizationService.findNearestCommunity.mockResolvedValue(null);

      const rewards = await service.calculateRewards(baseParams);

      const provinceTeamReward = rewards.find(
        (r) => r.rewardSource.rightType === RightType.PROVINCE_TEAM_RIGHT,
      );
      expect(provinceTeamReward).toBeDefined();
      expect(provinceTeamReward?.usdtAmount.amount).toBe(20 * 10);
      expect(provinceTeamReward?.userId).toBe(BigInt(300));
    });

    it('should calculate province area right (15 USDT + 1% hashpower)', async () => {
      mockReferralService.getReferralChain.mockResolvedValue({ ancestors: [] });
      mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null);
      mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null);
      mockAuthorizationService.findNearestCommunity.mockResolvedValue(null);

      const rewards = await service.calculateRewards(baseParams);

      const provinceAreaReward = rewards.find(
        (r) => r.rewardSource.rightType === RightType.PROVINCE_AREA_RIGHT,
      );
      expect(provinceAreaReward).toBeDefined();
      expect(provinceAreaReward?.usdtAmount.amount).toBe(15 * 10);
      expect(provinceAreaReward?.hashpowerAmount.value).toBe(1 * 10); // 1% * 10 trees = 10
    });

    it('should calculate city team right (40 USDT)', async () => {
      mockReferralService.getReferralChain.mockResolvedValue({ ancestors: [] });
      mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null);
      mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(BigInt(400));
      mockAuthorizationService.findNearestCommunity.mockResolvedValue(null);

      const rewards = await service.calculateRewards(baseParams);

      const cityTeamReward = rewards.find(
        (r) => r.rewardSource.rightType === RightType.CITY_TEAM_RIGHT,
      );
      expect(cityTeamReward).toBeDefined();
      expect(cityTeamReward?.usdtAmount.amount).toBe(40 * 10);
      expect(cityTeamReward?.userId).toBe(BigInt(400));
    });

    it('should calculate city area right (35 USDT + 2% hashpower)', async () => {
      mockReferralService.getReferralChain.mockResolvedValue({ ancestors: [] });
      mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null);
      mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null);
      mockAuthorizationService.findNearestCommunity.mockResolvedValue(null);

      const rewards = await service.calculateRewards(baseParams);

      const cityAreaReward = rewards.find(
        (r) => r.rewardSource.rightType === RightType.CITY_AREA_RIGHT,
      );
      expect(cityAreaReward).toBeDefined();
      expect(cityAreaReward?.usdtAmount.amount).toBe(35 * 10);
      expect(cityAreaReward?.hashpowerAmount.value).toBe(2 * 10); // 2% * 10 trees = 20
    });

    it('should calculate community right (80 USDT)', async () => {
      mockReferralService.getReferralChain.mockResolvedValue({ ancestors: [] });
      mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null);
      mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null);
      mockAuthorizationService.findNearestCommunity.mockResolvedValue(BigInt(500));

      const rewards = await service.calculateRewards(baseParams);

      const communityReward = rewards.find(
        (r) => r.rewardSource.rightType === RightType.COMMUNITY_RIGHT,
      );
      expect(communityReward).toBeDefined();
      expect(communityReward?.usdtAmount.amount).toBe(80 * 10);
      expect(communityReward?.userId).toBe(BigInt(500));
    });

    it('should assign to headquarters when no authorized holder found', async () => {
      mockReferralService.getReferralChain.mockResolvedValue({ ancestors: [] });
      mockAuthorizationService.findNearestAuthorizedProvince.mockResolvedValue(null);
      mockAuthorizationService.findNearestAuthorizedCity.mockResolvedValue(null);
      mockAuthorizationService.findNearestCommunity.mockResolvedValue(null);

      const rewards = await service.calculateRewards(baseParams);

      // Province team, city team, community should go to headquarters (ID=1)
      const provinceTeamReward = rewards.find(
        (r) => r.rewardSource.rightType === RightType.PROVINCE_TEAM_RIGHT,
      );
      const cityTeamReward = rewards.find(
        (r) => r.rewardSource.rightType === RightType.CITY_TEAM_RIGHT,
      );
      const communityReward = rewards.find(
        (r) => r.rewardSource.rightType === RightType.COMMUNITY_RIGHT,
      );

      expect(provinceTeamReward?.userId).toBe(BigInt(1));
      expect(cityTeamReward?.userId).toBe(BigInt(1));
      expect(communityReward?.userId).toBe(BigInt(1));
    });
  });
});
