import { Test, TestingModule } from '@nestjs/testing';
import { TeamStatisticsRepository } from '../../../src/infrastructure/repositories/team-statistics.repository';
import { PrismaService } from '../../../src/infrastructure/database/prisma.service';
import { MockPrismaService } from '../mocks/prisma.mock';
import { TeamStatistics } from '../../../src/domain';

describe('TeamStatisticsRepository (Integration)', () => {
  let repository: TeamStatisticsRepository;
  let mockPrisma: MockPrismaService;

  beforeEach(async () => {
    mockPrisma = new MockPrismaService();

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        TeamStatisticsRepository,
        {
          provide: PrismaService,
          useValue: mockPrisma,
        },
      ],
    }).compile();

    repository = module.get<TeamStatisticsRepository>(TeamStatisticsRepository);
  });

  afterEach(() => {
    mockPrisma.$cleanup();
  });

  describe('create', () => {
    it('should create new team statistics', async () => {
      const stats = await repository.create(100n, 'D25010100001');

      expect(stats).toBeDefined();
      expect(stats.userId).toBe(100n);
      expect(stats.directReferralCount).toBe(0);
      expect(stats.totalTeamCount).toBe(0);
      expect(stats.leaderboardScore).toBe(0);
    });
  });

  describe('save', () => {
    it('should save team statistics', async () => {
      const stats = TeamStatistics.create(100n, 'D25010100001');
      stats.addPersonalPlanting(10, '110000', '110100');

      const saved = await repository.save(stats);

      expect(saved).toBeDefined();
      expect(saved.userId).toBe(100n);
      expect(saved.personalPlantingCount).toBe(10);
    });

    it('should update existing statistics', async () => {
      // Create initial
      const stats = await repository.create(100n, 'D25010100001');

      // Create new instance and add planting
      const updated = TeamStatistics.create(100n, 'D25010100001');
      updated.addPersonalPlanting(20, '110000', '110100');

      const saved = await repository.save(updated);
      expect(saved.personalPlantingCount).toBe(20);
    });
  });

  describe('findByUserId', () => {
    it('should find statistics by user ID', async () => {
      await repository.create(100n, 'D25010100001');

      const found = await repository.findByUserId(100n);

      expect(found).toBeDefined();
      expect(found!.userId).toBe(100n);
    });

    it('should return null for non-existent user', async () => {
      const found = await repository.findByUserId(999n);
      expect(found).toBeNull();
    });
  });

  describe('findByUserIds', () => {
    it('should find statistics for multiple users', async () => {
      await repository.create(100n, 'D25010100001');
      await repository.create(101n, 'D25010100002');
      await repository.create(102n, 'D25010100003');

      const found = await repository.findByUserIds([100n, 101n]);

      expect(found.length).toBe(2);
      expect(found.map((s) => s.userId)).toContain(100n);
      expect(found.map((s) => s.userId)).toContain(101n);
    });
  });

  describe('getLeaderboard', () => {
    it('should return leaderboard sorted by score', async () => {
      // Create users with different scores
      const stats1 = TeamStatistics.create(100n, 'D25010100001');
      stats1.addPersonalPlanting(50, '110000', '110100');
      await repository.save(stats1);

      const stats2 = TeamStatistics.create(101n, 'D25010100002');
      stats2.addPersonalPlanting(100, '110000', '110100');
      await repository.save(stats2);

      const stats3 = TeamStatistics.create(102n, 'D25010100003');
      stats3.addPersonalPlanting(30, '110000', '110100');
      await repository.save(stats3);

      const leaderboard = await repository.getLeaderboard({ limit: 10 });

      expect(leaderboard.length).toBe(3);
      // Should be sorted by score descending
      expect(leaderboard[0].userId).toBe(101n);
      expect(leaderboard[0].rank).toBe(1);
      expect(leaderboard[1].userId).toBe(100n);
      expect(leaderboard[1].rank).toBe(2);
    });

    it('should respect limit and offset', async () => {
      for (let i = 0; i < 10; i++) {
        const stats = TeamStatistics.create(BigInt(100 + i), `D2501010000${i}`);
        stats.addPersonalPlanting(10 + i, '110000', '110100');
        await repository.save(stats);
      }

      const leaderboard = await repository.getLeaderboard({ limit: 5, offset: 2 });

      expect(leaderboard.length).toBe(5);
      expect(leaderboard[0].rank).toBe(3); // 1-indexed, offset by 2
    });
  });

  describe('getUserRank', () => {
    it('should return correct rank', async () => {
      const stats1 = TeamStatistics.create(100n, 'D25010100001');
      stats1.addPersonalPlanting(100, '110000', '110100');
      await repository.save(stats1);

      const stats2 = TeamStatistics.create(101n, 'D25010100002');
      stats2.addPersonalPlanting(50, '110000', '110100');
      await repository.save(stats2);

      const rank = await repository.getUserRank(101n);
      expect(rank).toBe(2);
    });

    it('should return null for non-existent user', async () => {
      const rank = await repository.getUserRank(999n);
      expect(rank).toBeNull();
    });
  });

  describe('batchUpdateTeamCounts', () => {
    it('should batch update team counts for multiple users', async () => {
      await repository.create(100n);
      await repository.create(101n);

      await repository.batchUpdateTeamCounts([
        { userId: 100n, countDelta: 10, provinceCode: '110000', cityCode: '110100' },
        { userId: 101n, countDelta: 20, provinceCode: '120000', cityCode: '120100' },
      ]);

      const stats100 = await repository.findByUserId(100n);
      const stats101 = await repository.findByUserId(101n);

      expect(stats100!.teamPlantingCount).toBe(10);
      expect(stats101!.teamPlantingCount).toBe(20);
    });
  });
});
