import { TeamRankingUseCase } from './TeamRankingUseCase';
import type { ITeamRatingRepository } from '@core/racing/domain/repositories/ITeamRatingRepository';
import type { ITeamRepository } from '@core/racing/domain/repositories/ITeamRepository';
import type { Logger } from '@core/shared/application';
import { TeamRatingSnapshot } from '@core/racing/domain/services/TeamRatingSnapshotCalculator';
import { TeamRatingValue } from '@core/racing/domain/value-objects/TeamRatingValue';
import { Team } from '@core/racing/domain/entities/Team';

// Mock repositories
class MockTeamRatingRepository implements ITeamRatingRepository {
  private snapshots: Map<string, TeamRatingSnapshot> = new Map();

  async findByTeamId(teamId: string): Promise<TeamRatingSnapshot | null> {
    return this.snapshots.get(teamId) || null;
  }

  async save(snapshot: TeamRatingSnapshot): Promise<TeamRatingSnapshot> {
    this.snapshots.set(snapshot.teamId, snapshot);
    return snapshot;
  }

  clear() {
    this.snapshots.clear();
  }

  setSnapshot(teamId: string, snapshot: TeamRatingSnapshot) {
    this.snapshots.set(teamId, snapshot);
  }
}

class MockTeamRepository implements ITeamRepository {
  private teams: Map<string, Team> = new Map();

  async findById(id: string): Promise<Team | null> {
    return this.teams.get(id) || null;
  }

  async findAll(): Promise<Team[]> {
    return Array.from(this.teams.values());
  }

  async findByLeagueId(leagueId: string): Promise<Team[]> {
    return Array.from(this.teams.values()).filter(t => 
      t.leagues.some(l => l.toString() === leagueId)
    );
  }

  async create(team: Team): Promise<Team> {
    this.teams.set(team.id, team);
    return team;
  }

  async update(team: Team): Promise<Team> {
    this.teams.set(team.id, team);
    return team;
  }

  async delete(id: string): Promise<void> {
    this.teams.delete(id);
  }

  async exists(id: string): Promise<boolean> {
    return this.teams.has(id);
  }

  clear() {
    this.teams.clear();
  }

  setTeam(team: Team) {
    this.teams.set(team.id, team);
  }
}

// Mock logger
const mockLogger: Logger = {
  info: () => {},
  error: () => {},
  warn: () => {},
  debug: () => {},
};

describe('TeamRankingUseCase', () => {
  let useCase: TeamRankingUseCase;
  let mockRatingRepo: MockTeamRatingRepository;
  let mockTeamRepo: MockTeamRepository;

  beforeEach(() => {
    mockRatingRepo = new MockTeamRatingRepository();
    mockTeamRepo = new MockTeamRepository();
    useCase = new TeamRankingUseCase(mockRatingRepo, mockTeamRepo, mockLogger);
  });

  afterEach(() => {
    mockRatingRepo.clear();
    mockTeamRepo.clear();
  });

  describe('getAllTeamRankings', () => {
    it('should return empty array when no teams exist', async () => {
      const result = await useCase.getAllTeamRankings();
      expect(result).toEqual([]);
    });

    it('should return empty array when no rating snapshots exist', async () => {
      const team = Team.create({
        id: 'team-123',
        name: 'Test Team',
        tag: 'TT',
        description: 'A test team',
        ownerId: 'driver-123',
        leagues: [],
      });
      mockTeamRepo.setTeam(team);

      const result = await useCase.getAllTeamRankings();
      expect(result).toEqual([]);
    });

    it('should return single team ranking', async () => {
      const teamId = 'team-123';
      const team = Team.create({
        id: teamId,
        name: 'Test Team',
        tag: 'TT',
        description: 'A test team',
        ownerId: 'driver-123',
        leagues: [],
      });
      mockTeamRepo.setTeam(team);

      const snapshot: TeamRatingSnapshot = {
        teamId,
        driving: TeamRatingValue.create(75),
        adminTrust: TeamRatingValue.create(80),
        overall: 76.5,
        lastUpdated: new Date('2024-01-01'),
        eventCount: 5,
      };
      mockRatingRepo.setSnapshot(teamId, snapshot);

      const result = await useCase.getAllTeamRankings();

      expect(result).toHaveLength(1);
      expect(result[0]).toEqual({
        teamId,
        teamName: 'Test Team',
        drivingRating: 75,
        adminTrustRating: 80,
        overallRating: 76.5,
        eventCount: 5,
        lastUpdated: new Date('2024-01-01'),
        overallRank: 1,
      });
    });

    it('should return multiple teams sorted by overall rating', async () => {
      // Team 1
      const team1 = Team.create({
        id: 'team-1',
        name: 'Team Alpha',
        tag: 'TA',
        description: 'Alpha team',
        ownerId: 'driver-1',
        leagues: [],
      });
      mockTeamRepo.setTeam(team1);
      mockRatingRepo.setSnapshot('team-1', {
        teamId: 'team-1',
        driving: TeamRatingValue.create(80),
        adminTrust: TeamRatingValue.create(70),
        overall: 77,
        lastUpdated: new Date('2024-01-01'),
        eventCount: 10,
      });

      // Team 2
      const team2 = Team.create({
        id: 'team-2',
        name: 'Team Beta',
        tag: 'TB',
        description: 'Beta team',
        ownerId: 'driver-2',
        leagues: [],
      });
      mockTeamRepo.setTeam(team2);
      mockRatingRepo.setSnapshot('team-2', {
        teamId: 'team-2',
        driving: TeamRatingValue.create(90),
        adminTrust: TeamRatingValue.create(85),
        overall: 88,
        lastUpdated: new Date('2024-01-02'),
        eventCount: 15,
      });

      // Team 3
      const team3 = Team.create({
        id: 'team-3',
        name: 'Team Gamma',
        tag: 'TG',
        description: 'Gamma team',
        ownerId: 'driver-3',
        leagues: [],
      });
      mockTeamRepo.setTeam(team3);
      mockRatingRepo.setSnapshot('team-3', {
        teamId: 'team-3',
        driving: TeamRatingValue.create(60),
        adminTrust: TeamRatingValue.create(65),
        overall: 61.5,
        lastUpdated: new Date('2024-01-03'),
        eventCount: 3,
      });

      const result = await useCase.getAllTeamRankings();

      expect(result).toHaveLength(3);
      
      // Should be sorted by overall rating descending
      expect(result[0]).toBeDefined();
      expect(result[0]!.teamId).toBe('team-2');
      expect(result[0]!.overallRank).toBe(1);
      expect(result[0]!.overallRating).toBe(88);

      expect(result[1]).toBeDefined();
      expect(result[1]!.teamId).toBe('team-1');
      expect(result[1]!.overallRank).toBe(2);
      expect(result[1]!.overallRating).toBe(77);

      expect(result[2]).toBeDefined();
      expect(result[2]!.teamId).toBe('team-3');
      expect(result[2]!.overallRank).toBe(3);
      expect(result[2]!.overallRating).toBe(61.5);
    });

    it('should handle teams without snapshots gracefully', async () => {
      // Team with snapshot
      const team1 = Team.create({
        id: 'team-1',
        name: 'Team With Rating',
        tag: 'TWR',
        description: 'Has rating',
        ownerId: 'driver-1',
        leagues: [],
      });
      mockTeamRepo.setTeam(team1);
      mockRatingRepo.setSnapshot('team-1', {
        teamId: 'team-1',
        driving: TeamRatingValue.create(70),
        adminTrust: TeamRatingValue.create(70),
        overall: 70,
        lastUpdated: new Date('2024-01-01'),
        eventCount: 5,
      });

      // Team without snapshot
      const team2 = Team.create({
        id: 'team-2',
        name: 'Team Without Rating',
        tag: 'TWR',
        description: 'No rating',
        ownerId: 'driver-2',
        leagues: [],
      });
      mockTeamRepo.setTeam(team2);

      const result = await useCase.getAllTeamRankings();

      expect(result).toHaveLength(1);
      expect(result[0]).toBeDefined();
      expect(result[0]!.teamId).toBe('team-1');
      expect(result[0]!.overallRank).toBe(1);
    });
  });

  describe('getTeamRanking', () => {
    it('should return null when team does not exist', async () => {
      const result = await useCase.getTeamRanking('non-existent-team');
      expect(result).toBeNull();
    });

    it('should return null when team exists but has no snapshot', async () => {
      const team = Team.create({
        id: 'team-123',
        name: 'Test Team',
        tag: 'TT',
        description: 'A test team',
        ownerId: 'driver-123',
        leagues: [],
      });
      mockTeamRepo.setTeam(team);

      const result = await useCase.getTeamRanking('team-123');
      expect(result).toBeNull();
    });

    it('should return team ranking with correct rank', async () => {
      // Setup multiple teams
      const teams = [
        { id: 'team-1', name: 'Team A', rating: 85 },
        { id: 'team-2', name: 'Team B', rating: 90 },
        { id: 'team-3', name: 'Team C', rating: 75 },
      ];

      for (const t of teams) {
        const team = Team.create({
          id: t.id,
          name: t.name,
          tag: t.name.substring(0, 2).toUpperCase(),
          description: `${t.name} description`,
          ownerId: `driver-${t.id}`,
          leagues: [],
        });
        mockTeamRepo.setTeam(team);
        mockRatingRepo.setSnapshot(t.id, {
          teamId: t.id,
          driving: TeamRatingValue.create(t.rating),
          adminTrust: TeamRatingValue.create(t.rating),
          overall: t.rating,
          lastUpdated: new Date('2024-01-01'),
          eventCount: 5,
        });
      }

      // Get ranking for team-2 (should be rank 1 with rating 90)
      const result = await useCase.getTeamRanking('team-2');

      expect(result).toBeDefined();
      expect(result?.teamId).toBe('team-2');
      expect(result?.teamName).toBe('Team B');
      expect(result?.overallRating).toBe(90);
      expect(result?.overallRank).toBe(1);
    });

    it('should calculate correct rank for middle team', async () => {
      // Setup teams
      const teams = [
        { id: 'team-1', name: 'Team A', rating: 90 },
        { id: 'team-2', name: 'Team B', rating: 80 },
        { id: 'team-3', name: 'Team C', rating: 70 },
      ];

      for (const t of teams) {
        const team = Team.create({
          id: t.id,
          name: t.name,
          tag: t.name.substring(0, 2).toUpperCase(),
          description: `${t.name} description`,
          ownerId: `driver-${t.id}`,
          leagues: [],
        });
        mockTeamRepo.setTeam(team);
        mockRatingRepo.setSnapshot(t.id, {
          teamId: t.id,
          driving: TeamRatingValue.create(t.rating),
          adminTrust: TeamRatingValue.create(t.rating),
          overall: t.rating,
          lastUpdated: new Date('2024-01-01'),
          eventCount: 5,
        });
      }

      // Get ranking for team-2 (should be rank 2)
      const result = await useCase.getTeamRanking('team-2');

      expect(result).toBeDefined();
      expect(result?.overallRank).toBe(2);
    });

    it('should return complete team ranking data', async () => {
      const teamId = 'team-123';
      const team = Team.create({
        id: teamId,
        name: 'Complete Team',
        tag: 'CT',
        description: 'Complete team description',
        ownerId: 'driver-123',
        leagues: [],
      });
      mockTeamRepo.setTeam(team);

      const snapshot: TeamRatingSnapshot = {
        teamId,
        driving: TeamRatingValue.create(82),
        adminTrust: TeamRatingValue.create(78),
        overall: 80.8,
        lastUpdated: new Date('2024-01-15T10:30:00Z'),
        eventCount: 25,
      };
      mockRatingRepo.setSnapshot(teamId, snapshot);

      const result = await useCase.getTeamRanking(teamId);

      expect(result).toEqual({
        teamId,
        teamName: 'Complete Team',
        drivingRating: 82,
        adminTrustRating: 78,
        overallRating: 80.8,
        eventCount: 25,
        lastUpdated: new Date('2024-01-15T10:30:00Z'),
        overallRank: 1,
      });
    });
  });

  describe('error handling', () => {
    it('should handle repository errors gracefully', async () => {
      // Mock repository to throw error
      const originalFindAll = mockTeamRepo.findAll.bind(mockTeamRepo);
      mockTeamRepo.findAll = async () => {
        throw new Error('Repository connection failed');
      };

      await expect(useCase.getAllTeamRankings()).rejects.toThrow('Repository connection failed');
    });
  });
});