import { Test, TestingModule } from '@nestjs/testing';
import { ReferralService } from '../../../src/application/services/referral.service';
import {
  REFERRAL_RELATIONSHIP_REPOSITORY,
  TEAM_STATISTICS_REPOSITORY,
  ReferralChainService,
  ReferralRelationship,
} from '../../../src/domain';
import { EventPublisherService } from '../../../src/infrastructure';
import { CreateReferralRelationshipCommand } from '../../../src/application/commands';
import { GetUserReferralInfoQuery, GetDirectReferralsQuery } from '../../../src/application/queries';

// Mock repository that works with domain aggregates
class MockReferralRepository {
  private data = new Map<bigint, ReferralRelationship>();
  private codeToUserId = new Map<string, bigint>();

  async save(relationship: ReferralRelationship): Promise<ReferralRelationship> {
    this.data.set(relationship.userId, relationship);
    this.codeToUserId.set(relationship.referralCode, relationship.userId);
    return relationship;
  }

  async findByUserId(userId: bigint): Promise<ReferralRelationship | null> {
    return this.data.get(userId) ?? null;
  }

  async findByReferralCode(code: string): Promise<ReferralRelationship | null> {
    const userId = this.codeToUserId.get(code);
    if (!userId) return null;
    return this.findByUserId(userId);
  }

  async findDirectReferrals(userId: bigint): Promise<ReferralRelationship[]> {
    const results: ReferralRelationship[] = [];
    for (const record of this.data.values()) {
      if (record.referrerId === userId) {
        results.push(record);
      }
    }
    return results;
  }

  async existsByReferralCode(code: string): Promise<boolean> {
    return this.codeToUserId.has(code);
  }

  async existsByUserId(userId: bigint): Promise<boolean> {
    return this.data.has(userId);
  }

  async getReferralChain(userId: bigint): Promise<bigint[]> {
    const record = this.data.get(userId);
    return record?.referralChain ?? [];
  }
}

class MockTeamStatsRepository {
  private data = new Map<bigint, { userId: bigint; directReferralCount: number; totalTeamCount: number; personalPlantingCount: number; teamPlantingCount: number; leaderboardScore: number; addDirectReferral: (id: bigint) => void; getDirectReferralStats: () => Map<bigint, number> }>();

  async create(userId: bigint) {
    const stats = {
      userId,
      directReferralCount: 0,
      totalTeamCount: 0,
      personalPlantingCount: 0,
      teamPlantingCount: 0,
      leaderboardScore: 0,
      addDirectReferral: function(id: bigint) { this.directReferralCount++; },
      getDirectReferralStats: () => new Map<bigint, number>(),
    };
    this.data.set(userId, stats);
    return stats;
  }

  async save(stats: { userId: bigint }) {
    const existing = this.data.get(stats.userId);
    if (existing) {
      Object.assign(existing, stats);
    }
    return existing;
  }

  async findByUserId(userId: bigint) {
    return this.data.get(userId) ?? null;
  }
}

class MockEventPublisher {
  async publishDomainEvents() {}
  async publishEvent() {}
}


describe('ReferralService (Integration)', () => {
  let service: ReferralService;
  let referralRepo: MockReferralRepository;
  let teamStatsRepo: MockTeamStatsRepository;

  beforeEach(async () => {
    referralRepo = new MockReferralRepository();
    teamStatsRepo = new MockTeamStatsRepository();

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ReferralService,
        ReferralChainService,
        {
          provide: REFERRAL_RELATIONSHIP_REPOSITORY,
          useValue: referralRepo,
        },
        {
          provide: TEAM_STATISTICS_REPOSITORY,
          useValue: teamStatsRepo,
        },
        {
          provide: EventPublisherService,
          useClass: MockEventPublisher,
        },
      ],
    }).compile();

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

  describe('createReferralRelationship', () => {
    it('should create referral relationship without referrer', async () => {
      const command = new CreateReferralRelationshipCommand(100n, 12345678, null);
      const result = await service.createReferralRelationship(command);

      expect(result).toBeDefined();
      expect(result.referralCode).toBeDefined();
      expect(result.referralCode.length).toBeGreaterThanOrEqual(6);
    });

    it('should create referral relationship with valid referrer code', async () => {
      // First create a referrer
      const referrerCommand = new CreateReferralRelationshipCommand(50n, 11111111, null);
      const referrerResult = await service.createReferralRelationship(referrerCommand);

      // Then create with referrer code
      const command = new CreateReferralRelationshipCommand(100n, 12345678, referrerResult.referralCode);
      const result = await service.createReferralRelationship(command);

      expect(result).toBeDefined();
      expect(result.referralCode).toBeDefined();
    });

    it('should throw error if user already has referral relationship', async () => {
      const command = new CreateReferralRelationshipCommand(100n, 12345678, null);
      await service.createReferralRelationship(command);

      await expect(service.createReferralRelationship(command)).rejects.toThrow('用户已存在推荐关系');
    });

    it('should throw error for invalid referral code', async () => {
      const command = new CreateReferralRelationshipCommand(100n, 12345678, 'INVALID_CODE');

      await expect(service.createReferralRelationship(command)).rejects.toThrow('推荐码不存在');
    });
  });

  describe('getUserReferralInfo', () => {
    it('should return user referral info', async () => {
      // Create user first
      const createCommand = new CreateReferralRelationshipCommand(100n, 12345678, null);
      await service.createReferralRelationship(createCommand);

      const query = new GetUserReferralInfoQuery(12345678);
      const result = await service.getUserReferralInfo(query);

      expect(result).toBeDefined();
      expect(result.userId).toBe('100');
      expect(result.referralCode).toBeDefined();
      expect(result.directReferralCount).toBe(0);
    });

    it('should throw error for non-existent user', async () => {
      const query = new GetUserReferralInfoQuery(99999999);

      await expect(service.getUserReferralInfo(query)).rejects.toThrow('用户推荐关系不存在');
    });
  });

  describe('getDirectReferrals', () => {
    it('should return direct referrals list', async () => {
      // Create referrer
      const referrerCommand = new CreateReferralRelationshipCommand(50n, 11111111, null);
      const referrerResult = await service.createReferralRelationship(referrerCommand);

      // Create direct referrals
      await service.createReferralRelationship(new CreateReferralRelationshipCommand(100n, 12345678, referrerResult.referralCode));
      await service.createReferralRelationship(new CreateReferralRelationshipCommand(101n, 12345679, referrerResult.referralCode));

      const query = new GetDirectReferralsQuery(50n);
      const result = await service.getDirectReferrals(query);

      expect(result.referrals.length).toBe(2);
      expect(result.total).toBe(2);
    });
  });

  describe('validateReferralCode', () => {
    it('should return true for valid code', async () => {
      const createCommand = new CreateReferralRelationshipCommand(100n, 12345678, null);
      const { referralCode } = await service.createReferralRelationship(createCommand);

      const isValid = await service.validateReferralCode(referralCode);
      expect(isValid).toBe(true);
    });

    it('should return false for invalid code', async () => {
      const isValid = await service.validateReferralCode('INVALID');
      expect(isValid).toBe(false);
    });
  });
});
