/**
 * Integration Test: Global Leaderboards Use Case Orchestration
 *
 * Tests the orchestration logic of global leaderboards-related Use Cases:
 * - GetGlobalLeaderboardsUseCase: Retrieves top drivers and teams for the main leaderboards page
 * - Validates that Use Cases correctly interact with their Ports (Repositories, Event Publishers)
 * - Uses In-Memory adapters for fast, deterministic testing
 *
 * Focus: Business logic orchestration, NOT UI rendering
 */

import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
import { InMemoryLeaderboardsRepository } from '../../../adapters/leaderboards/persistence/inmemory/InMemoryLeaderboardsRepository';
import { InMemoryLeaderboardsEventPublisher } from '../../../adapters/leaderboards/events/InMemoryLeaderboardsEventPublisher';
import { GetGlobalLeaderboardsUseCase } from '../../../core/leaderboards/application/use-cases/GetGlobalLeaderboardsUseCase';

describe('Global Leaderboards Use Case Orchestration', () => {
  let leaderboardsRepository: InMemoryLeaderboardsRepository;
  let eventPublisher: InMemoryLeaderboardsEventPublisher;
  let getGlobalLeaderboardsUseCase: GetGlobalLeaderboardsUseCase;

  beforeAll(() => {
    leaderboardsRepository = new InMemoryLeaderboardsRepository();
    eventPublisher = new InMemoryLeaderboardsEventPublisher();
    getGlobalLeaderboardsUseCase = new GetGlobalLeaderboardsUseCase({
      leaderboardsRepository,
      eventPublisher,
    });
  });

  beforeEach(() => {
    leaderboardsRepository.clear();
    eventPublisher.clear();
  });

  describe('GetGlobalLeaderboardsUseCase - Success Path', () => {
    it('should retrieve top drivers and teams with complete data', async () => {
      // Scenario: System has multiple drivers and teams with complete data
      // Given: Multiple drivers exist with various ratings and team affiliations
      leaderboardsRepository.addDriver({
        id: 'driver-1',
        name: 'John Smith',
        rating: 5.0,
        teamId: 'team-1',
        teamName: 'Racing Team A',
        raceCount: 50,
      });
      leaderboardsRepository.addDriver({
        id: 'driver-2',
        name: 'Jane Doe',
        rating: 4.8,
        teamId: 'team-2',
        teamName: 'Speed Squad',
        raceCount: 45,
      });
      leaderboardsRepository.addDriver({
        id: 'driver-3',
        name: 'Bob Johnson',
        rating: 4.5,
        teamId: 'team-1',
        teamName: 'Racing Team A',
        raceCount: 40,
      });

      // And: Multiple teams exist with various ratings and member counts
      leaderboardsRepository.addTeam({
        id: 'team-1',
        name: 'Racing Team A',
        rating: 4.9,
        memberCount: 5,
        raceCount: 100,
      });
      leaderboardsRepository.addTeam({
        id: 'team-2',
        name: 'Speed Squad',
        rating: 4.7,
        memberCount: 3,
        raceCount: 80,
      });
      leaderboardsRepository.addTeam({
        id: 'team-3',
        name: 'Champions League',
        rating: 4.3,
        memberCount: 4,
        raceCount: 60,
      });

      // When: GetGlobalLeaderboardsUseCase.execute() is called
      const result = await getGlobalLeaderboardsUseCase.execute();

      // Then: The result should contain top 10 drivers (but we only have 3)
      expect(result.drivers).toHaveLength(3);

      // And: The result should contain top 10 teams (but we only have 3)
      expect(result.teams).toHaveLength(3);

      // And: Driver entries should include rank, name, rating, and team affiliation
      expect(result.drivers[0]).toMatchObject({
        rank: 1,
        id: 'driver-1',
        name: 'John Smith',
        rating: 5.0,
        teamId: 'team-1',
        teamName: 'Racing Team A',
        raceCount: 50,
      });

      // And: Team entries should include rank, name, rating, and member count
      expect(result.teams[0]).toMatchObject({
        rank: 1,
        id: 'team-1',
        name: 'Racing Team A',
        rating: 4.9,
        memberCount: 5,
        raceCount: 100,
      });

      // And: EventPublisher should emit GlobalLeaderboardsAccessedEvent
      expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
    });

    it('should retrieve top drivers and teams with minimal data', async () => {
      // Scenario: System has minimal data
      // Given: Only a few drivers exist
      leaderboardsRepository.addDriver({
        id: 'driver-1',
        name: 'John Smith',
        rating: 5.0,
        raceCount: 10,
      });

      // And: Only a few teams exist
      leaderboardsRepository.addTeam({
        id: 'team-1',
        name: 'Racing Team A',
        rating: 4.9,
        memberCount: 2,
        raceCount: 20,
      });

      // When: GetGlobalLeaderboardsUseCase.execute() is called
      const result = await getGlobalLeaderboardsUseCase.execute();

      // Then: The result should contain all available drivers
      expect(result.drivers).toHaveLength(1);
      expect(result.drivers[0].name).toBe('John Smith');

      // And: The result should contain all available teams
      expect(result.teams).toHaveLength(1);
      expect(result.teams[0].name).toBe('Racing Team A');

      // And: EventPublisher should emit GlobalLeaderboardsAccessedEvent
      expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
    });

    it('should retrieve top drivers and teams when there are many', async () => {
      // Scenario: System has many drivers and teams
      // Given: More than 10 drivers exist
      for (let i = 1; i <= 15; i++) {
        leaderboardsRepository.addDriver({
          id: `driver-${i}`,
          name: `Driver ${i}`,
          rating: 5.0 - i * 0.1,
          raceCount: 10 + i,
        });
      }

      // And: More than 10 teams exist
      for (let i = 1; i <= 15; i++) {
        leaderboardsRepository.addTeam({
          id: `team-${i}`,
          name: `Team ${i}`,
          rating: 5.0 - i * 0.1,
          memberCount: 2 + i,
          raceCount: 20 + i,
        });
      }

      // When: GetGlobalLeaderboardsUseCase.execute() is called
      const result = await getGlobalLeaderboardsUseCase.execute();

      // Then: The result should contain only top 10 drivers
      expect(result.drivers).toHaveLength(10);

      // And: The result should contain only top 10 teams
      expect(result.teams).toHaveLength(10);

      // And: Drivers should be sorted by rating (highest first)
      expect(result.drivers[0].rating).toBe(4.9); // Driver 1
      expect(result.drivers[9].rating).toBe(4.0); // Driver 10

      // And: Teams should be sorted by rating (highest first)
      expect(result.teams[0].rating).toBe(4.9); // Team 1
      expect(result.teams[9].rating).toBe(4.0); // Team 10

      // And: EventPublisher should emit GlobalLeaderboardsAccessedEvent
      expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
    });

    it('should retrieve top drivers and teams with consistent ranking order', async () => {
      // Scenario: Verify ranking consistency
      // Given: Multiple drivers exist with various ratings
      leaderboardsRepository.addDriver({
        id: 'driver-1',
        name: 'Driver A',
        rating: 5.0,
        raceCount: 10,
      });
      leaderboardsRepository.addDriver({
        id: 'driver-2',
        name: 'Driver B',
        rating: 4.8,
        raceCount: 10,
      });
      leaderboardsRepository.addDriver({
        id: 'driver-3',
        name: 'Driver C',
        rating: 4.5,
        raceCount: 10,
      });

      // And: Multiple teams exist with various ratings
      leaderboardsRepository.addTeam({
        id: 'team-1',
        name: 'Team A',
        rating: 4.9,
        memberCount: 2,
        raceCount: 20,
      });
      leaderboardsRepository.addTeam({
        id: 'team-2',
        name: 'Team B',
        rating: 4.7,
        memberCount: 2,
        raceCount: 20,
      });
      leaderboardsRepository.addTeam({
        id: 'team-3',
        name: 'Team C',
        rating: 4.3,
        memberCount: 2,
        raceCount: 20,
      });

      // When: GetGlobalLeaderboardsUseCase.execute() is called
      const result = await getGlobalLeaderboardsUseCase.execute();

      // Then: Driver ranks should be sequential (1, 2, 3...)
      expect(result.drivers[0].rank).toBe(1);
      expect(result.drivers[1].rank).toBe(2);
      expect(result.drivers[2].rank).toBe(3);

      // And: Team ranks should be sequential (1, 2, 3...)
      expect(result.teams[0].rank).toBe(1);
      expect(result.teams[1].rank).toBe(2);
      expect(result.teams[2].rank).toBe(3);

      // And: No duplicate ranks should appear
      const driverRanks = result.drivers.map((d) => d.rank);
      const teamRanks = result.teams.map((t) => t.rank);
      expect(new Set(driverRanks).size).toBe(driverRanks.length);
      expect(new Set(teamRanks).size).toBe(teamRanks.length);

      // And: EventPublisher should emit GlobalLeaderboardsAccessedEvent
      expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
    });

    it('should retrieve top drivers and teams with accurate data', async () => {
      // Scenario: Verify data accuracy
      // Given: Drivers exist with valid ratings and names
      leaderboardsRepository.addDriver({
        id: 'driver-1',
        name: 'John Smith',
        rating: 5.0,
        raceCount: 50,
      });

      // And: Teams exist with valid ratings and member counts
      leaderboardsRepository.addTeam({
        id: 'team-1',
        name: 'Racing Team A',
        rating: 4.9,
        memberCount: 5,
        raceCount: 100,
      });

      // When: GetGlobalLeaderboardsUseCase.execute() is called
      const result = await getGlobalLeaderboardsUseCase.execute();

      // Then: All driver ratings should be valid numbers
      expect(result.drivers[0].rating).toBeGreaterThan(0);
      expect(typeof result.drivers[0].rating).toBe('number');

      // And: All team ratings should be valid numbers
      expect(result.teams[0].rating).toBeGreaterThan(0);
      expect(typeof result.teams[0].rating).toBe('number');

      // And: All team member counts should be valid numbers
      expect(result.teams[0].memberCount).toBeGreaterThan(0);
      expect(typeof result.teams[0].memberCount).toBe('number');

      // And: All names should be non-empty strings
      expect(result.drivers[0].name).toBeTruthy();
      expect(typeof result.drivers[0].name).toBe('string');
      expect(result.teams[0].name).toBeTruthy();
      expect(typeof result.teams[0].name).toBe('string');

      // And: EventPublisher should emit GlobalLeaderboardsAccessedEvent
      expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
    });
  });

  describe('GetGlobalLeaderboardsUseCase - Edge Cases', () => {
    it('should handle system with no drivers', async () => {
      // Scenario: System has no drivers
      // Given: No drivers exist in the system
      // And: Teams exist
      leaderboardsRepository.addTeam({
        id: 'team-1',
        name: 'Racing Team A',
        rating: 4.9,
        memberCount: 5,
        raceCount: 100,
      });

      // When: GetGlobalLeaderboardsUseCase.execute() is called
      const result = await getGlobalLeaderboardsUseCase.execute();

      // Then: The result should contain empty drivers list
      expect(result.drivers).toHaveLength(0);

      // And: The result should contain top teams
      expect(result.teams).toHaveLength(1);
      expect(result.teams[0].name).toBe('Racing Team A');

      // And: EventPublisher should emit GlobalLeaderboardsAccessedEvent
      expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
    });

    it('should handle system with no teams', async () => {
      // Scenario: System has no teams
      // Given: Drivers exist
      leaderboardsRepository.addDriver({
        id: 'driver-1',
        name: 'John Smith',
        rating: 5.0,
        raceCount: 50,
      });

      // And: No teams exist in the system
      // When: GetGlobalLeaderboardsUseCase.execute() is called
      const result = await getGlobalLeaderboardsUseCase.execute();

      // Then: The result should contain top drivers
      expect(result.drivers).toHaveLength(1);
      expect(result.drivers[0].name).toBe('John Smith');

      // And: The result should contain empty teams list
      expect(result.teams).toHaveLength(0);

      // And: EventPublisher should emit GlobalLeaderboardsAccessedEvent
      expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
    });

    it('should handle system with no data at all', async () => {
      // Scenario: System has absolutely no data
      // Given: No drivers exist
      // And: No teams exist
      // When: GetGlobalLeaderboardsUseCase.execute() is called
      const result = await getGlobalLeaderboardsUseCase.execute();

      // Then: The result should contain empty drivers list
      expect(result.drivers).toHaveLength(0);

      // And: The result should contain empty teams list
      expect(result.teams).toHaveLength(0);

      // And: EventPublisher should emit GlobalLeaderboardsAccessedEvent
      expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
    });

    it('should handle drivers with same rating', async () => {
      // Scenario: Multiple drivers with identical ratings
      // Given: Multiple drivers exist with the same rating
      leaderboardsRepository.addDriver({
        id: 'driver-1',
        name: 'Zoe',
        rating: 5.0,
        raceCount: 50,
      });
      leaderboardsRepository.addDriver({
        id: 'driver-2',
        name: 'Alice',
        rating: 5.0,
        raceCount: 45,
      });
      leaderboardsRepository.addDriver({
        id: 'driver-3',
        name: 'Bob',
        rating: 5.0,
        raceCount: 40,
      });

      // When: GetGlobalLeaderboardsUseCase.execute() is called
      const result = await getGlobalLeaderboardsUseCase.execute();

      // Then: Drivers should be sorted by rating
      expect(result.drivers[0].rating).toBe(5.0);
      expect(result.drivers[1].rating).toBe(5.0);
      expect(result.drivers[2].rating).toBe(5.0);

      // And: Drivers with same rating should have consistent ordering (by name)
      expect(result.drivers[0].name).toBe('Alice');
      expect(result.drivers[1].name).toBe('Bob');
      expect(result.drivers[2].name).toBe('Zoe');

      // And: EventPublisher should emit GlobalLeaderboardsAccessedEvent
      expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
    });

    it('should handle teams with same rating', async () => {
      // Scenario: Multiple teams with identical ratings
      // Given: Multiple teams exist with the same rating
      leaderboardsRepository.addTeam({
        id: 'team-1',
        name: 'Zeta Team',
        rating: 4.9,
        memberCount: 5,
        raceCount: 100,
      });
      leaderboardsRepository.addTeam({
        id: 'team-2',
        name: 'Alpha Team',
        rating: 4.9,
        memberCount: 3,
        raceCount: 80,
      });
      leaderboardsRepository.addTeam({
        id: 'team-3',
        name: 'Beta Team',
        rating: 4.9,
        memberCount: 4,
        raceCount: 60,
      });

      // When: GetGlobalLeaderboardsUseCase.execute() is called
      const result = await getGlobalLeaderboardsUseCase.execute();

      // Then: Teams should be sorted by rating
      expect(result.teams[0].rating).toBe(4.9);
      expect(result.teams[1].rating).toBe(4.9);
      expect(result.teams[2].rating).toBe(4.9);

      // And: Teams with same rating should have consistent ordering (by name)
      expect(result.teams[0].name).toBe('Alpha Team');
      expect(result.teams[1].name).toBe('Beta Team');
      expect(result.teams[2].name).toBe('Zeta Team');

      // And: EventPublisher should emit GlobalLeaderboardsAccessedEvent
      expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(1);
    });
  });

  describe('GetGlobalLeaderboardsUseCase - Error Handling', () => {
    it('should handle repository errors gracefully', async () => {
      // Scenario: Repository throws error
      // Given: LeaderboardsRepository throws an error during query
      const originalFindAllDrivers = leaderboardsRepository.findAllDrivers.bind(leaderboardsRepository);
      leaderboardsRepository.findAllDrivers = async () => {
        throw new Error('Repository error');
      };

      // When: GetGlobalLeaderboardsUseCase.execute() is called
      try {
        await getGlobalLeaderboardsUseCase.execute();
        // Should not reach here
        expect(true).toBe(false);
      } catch (error) {
        // Then: Should propagate the error appropriately
        expect(error).toBeInstanceOf(Error);
        expect((error as Error).message).toBe('Repository error');
      }

      // And: EventPublisher should NOT emit any events
      expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(0);

      // Restore original method
      leaderboardsRepository.findAllDrivers = originalFindAllDrivers;
    });

    it('should handle team repository errors gracefully', async () => {
      // Scenario: Team repository throws error
      // Given: LeaderboardsRepository throws an error during query
      const originalFindAllTeams = leaderboardsRepository.findAllTeams.bind(leaderboardsRepository);
      leaderboardsRepository.findAllTeams = async () => {
        throw new Error('Team repository error');
      };

      // When: GetGlobalLeaderboardsUseCase.execute() is called
      try {
        await getGlobalLeaderboardsUseCase.execute();
        // Should not reach here
        expect(true).toBe(false);
      } catch (error) {
        // Then: Should propagate the error appropriately
        expect(error).toBeInstanceOf(Error);
        expect((error as Error).message).toBe('Team repository error');
      }

      // And: EventPublisher should NOT emit any events
      expect(eventPublisher.getGlobalLeaderboardsAccessedEventCount()).toBe(0);

      // Restore original method
      leaderboardsRepository.findAllTeams = originalFindAllTeams;
    });
  });

  describe('Global Leaderboards Data Orchestration', () => {
    it('should correctly calculate driver rankings based on rating', async () => {
      // Scenario: Driver ranking calculation
      // Given: Drivers exist with ratings: 5.0, 4.8, 4.5, 4.2, 4.0
      const ratings = [5.0, 4.8, 4.5, 4.2, 4.0];
      ratings.forEach((rating, index) => {
        leaderboardsRepository.addDriver({
          id: `driver-${index}`,
          name: `Driver ${index}`,
          rating,
          raceCount: 10 + index,
        });
      });

      // When: GetGlobalLeaderboardsUseCase.execute() is called
      const result = await getGlobalLeaderboardsUseCase.execute();

      // Then: Driver rankings should be correct
      expect(result.drivers[0].rank).toBe(1);
      expect(result.drivers[0].rating).toBe(5.0);
      expect(result.drivers[1].rank).toBe(2);
      expect(result.drivers[1].rating).toBe(4.8);
      expect(result.drivers[2].rank).toBe(3);
      expect(result.drivers[2].rating).toBe(4.5);
      expect(result.drivers[3].rank).toBe(4);
      expect(result.drivers[3].rating).toBe(4.2);
      expect(result.drivers[4].rank).toBe(5);
      expect(result.drivers[4].rating).toBe(4.0);
    });

    it('should correctly calculate team rankings based on rating', async () => {
      // Scenario: Team ranking calculation
      // Given: Teams exist with ratings: 4.9, 4.7, 4.6, 4.3, 4.1
      const ratings = [4.9, 4.7, 4.6, 4.3, 4.1];
      ratings.forEach((rating, index) => {
        leaderboardsRepository.addTeam({
          id: `team-${index}`,
          name: `Team ${index}`,
          rating,
          memberCount: 2 + index,
          raceCount: 20 + index,
        });
      });

      // When: GetGlobalLeaderboardsUseCase.execute() is called
      const result = await getGlobalLeaderboardsUseCase.execute();

      // Then: Team rankings should be correct
      expect(result.teams[0].rank).toBe(1);
      expect(result.teams[0].rating).toBe(4.9);
      expect(result.teams[1].rank).toBe(2);
      expect(result.teams[1].rating).toBe(4.7);
      expect(result.teams[2].rank).toBe(3);
      expect(result.teams[2].rating).toBe(4.6);
      expect(result.teams[3].rank).toBe(4);
      expect(result.teams[3].rating).toBe(4.3);
      expect(result.teams[4].rank).toBe(5);
      expect(result.teams[4].rating).toBe(4.1);
    });

    it('should correctly format driver entries with team affiliation', async () => {
      // Scenario: Driver entry formatting
      // Given: A driver exists with team affiliation
      leaderboardsRepository.addDriver({
        id: 'driver-1',
        name: 'John Smith',
        rating: 5.0,
        teamId: 'team-1',
        teamName: 'Racing Team A',
        raceCount: 50,
      });

      // When: GetGlobalLeaderboardsUseCase.execute() is called
      const result = await getGlobalLeaderboardsUseCase.execute();

      // Then: Driver entry should include all required fields
      const driver = result.drivers[0];
      expect(driver.rank).toBe(1);
      expect(driver.id).toBe('driver-1');
      expect(driver.name).toBe('John Smith');
      expect(driver.rating).toBe(5.0);
      expect(driver.teamId).toBe('team-1');
      expect(driver.teamName).toBe('Racing Team A');
      expect(driver.raceCount).toBe(50);
    });

    it('should correctly format team entries with member count', async () => {
      // Scenario: Team entry formatting
      // Given: A team exists with members
      leaderboardsRepository.addTeam({
        id: 'team-1',
        name: 'Racing Team A',
        rating: 4.9,
        memberCount: 5,
        raceCount: 100,
      });

      // When: GetGlobalLeaderboardsUseCase.execute() is called
      const result = await getGlobalLeaderboardsUseCase.execute();

      // Then: Team entry should include all required fields
      const team = result.teams[0];
      expect(team.rank).toBe(1);
      expect(team.id).toBe('team-1');
      expect(team.name).toBe('Racing Team A');
      expect(team.rating).toBe(4.9);
      expect(team.memberCount).toBe(5);
      expect(team.raceCount).toBe(100);
    });

    it('should limit results to top 10 drivers and teams', async () => {
      // Scenario: Result limiting
      // Given: More than 10 drivers exist
      for (let i = 1; i <= 15; i++) {
        leaderboardsRepository.addDriver({
          id: `driver-${i}`,
          name: `Driver ${i}`,
          rating: 5.0 - i * 0.1,
          raceCount: 10 + i,
        });
      }

      // And: More than 10 teams exist
      for (let i = 1; i <= 15; i++) {
        leaderboardsRepository.addTeam({
          id: `team-${i}`,
          name: `Team ${i}`,
          rating: 5.0 - i * 0.1,
          memberCount: 2 + i,
          raceCount: 20 + i,
        });
      }

      // When: GetGlobalLeaderboardsUseCase.execute() is called
      const result = await getGlobalLeaderboardsUseCase.execute();

      // Then: Only top 10 drivers should be returned
      expect(result.drivers).toHaveLength(10);

      // And: Only top 10 teams should be returned
      expect(result.teams).toHaveLength(10);

      // And: Results should be sorted by rating (highest first)
      expect(result.drivers[0].rating).toBe(4.9); // Driver 1
      expect(result.drivers[9].rating).toBe(4.0); // Driver 10
      expect(result.teams[0].rating).toBe(4.9); // Team 1
      expect(result.teams[9].rating).toBe(4.0); // Team 10
    });
  });
});
