/**
 * Integration Test: Driver Profile Use Cases Orchestration
 *
 * Tests the orchestration logic of driver profile-related Use Cases:
 * - GetProfileOverviewUseCase: Retrieves driver profile overview with statistics, teams, friends, and extended info
 * - UpdateDriverProfileUseCase: Updates driver profile information
 * - Validates that Use Cases correctly interact with their Ports (Repositories, Providers, other Use Cases)
 * - Uses In-Memory adapters for fast, deterministic testing
 *
 * Focus: Business logic orchestration, NOT UI rendering
 */

import { describe, it, expect, beforeAll, beforeEach } from 'vitest';
import { InMemoryDriverRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryDriverRepository';
import { InMemoryTeamRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamRepository';
import { InMemoryTeamMembershipRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryTeamMembershipRepository';
import { InMemorySocialGraphRepository } from '../../../adapters/social/persistence/inmemory/InMemorySocialAndFeed';
import { InMemoryDriverExtendedProfileProvider } from '../../../adapters/racing/ports/InMemoryDriverExtendedProfileProvider';
import { InMemoryDriverStatsRepository } from '../../../adapters/racing/persistence/inmemory/InMemoryDriverStatsRepository';
import { GetProfileOverviewUseCase } from '../../../core/racing/application/use-cases/GetProfileOverviewUseCase';
import { UpdateDriverProfileUseCase } from '../../../core/racing/application/use-cases/UpdateDriverProfileUseCase';
import { DriverStatsUseCase } from '../../../core/racing/application/use-cases/DriverStatsUseCase';
import { RankingUseCase } from '../../../core/racing/application/use-cases/RankingUseCase';
import { Driver } from '../../../core/racing/domain/entities/Driver';
import { Team } from '../../../core/racing/domain/entities/Team';
import { Logger } from '../../../core/shared/domain/Logger';

describe('Driver Profile Use Cases Orchestration', () => {
  let driverRepository: InMemoryDriverRepository;
  let teamRepository: InMemoryTeamRepository;
  let teamMembershipRepository: InMemoryTeamMembershipRepository;
  let socialRepository: InMemorySocialGraphRepository;
  let driverExtendedProfileProvider: InMemoryDriverExtendedProfileProvider;
  let driverStatsRepository: InMemoryDriverStatsRepository;
  let driverStatsUseCase: DriverStatsUseCase;
  let rankingUseCase: RankingUseCase;
  let getProfileOverviewUseCase: GetProfileOverviewUseCase;
  let updateDriverProfileUseCase: UpdateDriverProfileUseCase;
  let mockLogger: Logger;

  beforeAll(() => {
    mockLogger = {
      info: () => {},
      debug: () => {},
      warn: () => {},
      error: () => {},
    } as unknown as Logger;
    
    driverRepository = new InMemoryDriverRepository(mockLogger);
    teamRepository = new InMemoryTeamRepository(mockLogger);
    teamMembershipRepository = new InMemoryTeamMembershipRepository(mockLogger);
    socialRepository = new InMemorySocialGraphRepository(mockLogger);
    driverExtendedProfileProvider = new InMemoryDriverExtendedProfileProvider(mockLogger);
    driverStatsRepository = new InMemoryDriverStatsRepository(mockLogger);
    
    driverStatsUseCase = new DriverStatsUseCase(
      {} as any,
      {} as any,
      driverStatsRepository,
      mockLogger
    );
    
    rankingUseCase = new RankingUseCase(
      {} as any,
      {} as any,
      driverStatsRepository,
      mockLogger
    );

    getProfileOverviewUseCase = new GetProfileOverviewUseCase(
      driverRepository,
      teamRepository,
      teamMembershipRepository,
      socialRepository,
      driverExtendedProfileProvider,
      driverStatsUseCase,
      rankingUseCase
    );
    
    updateDriverProfileUseCase = new UpdateDriverProfileUseCase(driverRepository, mockLogger);
  });

  beforeEach(() => {
    driverRepository.clear();
    teamRepository.clear();
    teamMembershipRepository.clear();
    socialRepository.clear();
    driverExtendedProfileProvider.clear();
    driverStatsRepository.clear();
  });

  describe('UpdateDriverProfileUseCase - Success Path', () => {
    it('should update driver bio', async () => {
      // Scenario: Update driver bio
      // Given: A driver exists with bio
      const driverId = 'd2';
      const driver = Driver.create({ id: driverId, iracingId: '2', name: 'Update Driver', country: 'US', bio: 'Original bio' });
      await driverRepository.create(driver);

      // When: UpdateDriverProfileUseCase.execute() is called with new bio
      const result = await updateDriverProfileUseCase.execute({
        driverId,
        bio: 'Updated bio',
      });

      // Then: The operation should succeed
      expect(result.isOk()).toBe(true);

      // And: The driver's bio should be updated
      const updatedDriver = await driverRepository.findById(driverId);
      expect(updatedDriver).not.toBeNull();
      expect(updatedDriver!.bio?.toString()).toBe('Updated bio');
    });

    it('should update driver country', async () => {
      // Scenario: Update driver country
      // Given: A driver exists with country
      const driverId = 'd3';
      const driver = Driver.create({ id: driverId, iracingId: '3', name: 'Country Driver', country: 'US' });
      await driverRepository.create(driver);

      // When: UpdateDriverProfileUseCase.execute() is called with new country
      const result = await updateDriverProfileUseCase.execute({
        driverId,
        country: 'DE',
      });

      // Then: The operation should succeed
      expect(result.isOk()).toBe(true);

      // And: The driver's country should be updated
      const updatedDriver = await driverRepository.findById(driverId);
      expect(updatedDriver).not.toBeNull();
      expect(updatedDriver!.country.toString()).toBe('DE');
    });

    it('should update multiple profile fields at once', async () => {
      // Scenario: Update multiple fields
      // Given: A driver exists
      const driverId = 'd4';
      const driver = Driver.create({ id: driverId, iracingId: '4', name: 'Multi Update Driver', country: 'US', bio: 'Original bio' });
      await driverRepository.create(driver);

      // When: UpdateDriverProfileUseCase.execute() is called with multiple updates
      const result = await updateDriverProfileUseCase.execute({
        driverId,
        bio: 'Updated bio',
        country: 'FR',
      });

      // Then: The operation should succeed
      expect(result.isOk()).toBe(true);

      // And: Both fields should be updated
      const updatedDriver = await driverRepository.findById(driverId);
      expect(updatedDriver).not.toBeNull();
      expect(updatedDriver!.bio?.toString()).toBe('Updated bio');
      expect(updatedDriver!.country.toString()).toBe('FR');
    });
  });

  describe('UpdateDriverProfileUseCase - Validation', () => {
    it('should reject update with empty bio', async () => {
      // Scenario: Empty bio
      // Given: A driver exists
      const driverId = 'd5';
      const driver = Driver.create({ id: driverId, iracingId: '5', name: 'Empty Bio Driver', country: 'US' });
      await driverRepository.create(driver);

      // When: UpdateDriverProfileUseCase.execute() is called with empty bio
      const result = await updateDriverProfileUseCase.execute({
        driverId,
        bio: '',
      });

      // Then: Should return error
      expect(result.isErr()).toBe(true);
      const error = result.unwrapErr();
      expect(error.code).toBe('INVALID_PROFILE_DATA');
      expect(error.details.message).toBe('Profile data is invalid');
    });

    it('should reject update with empty country', async () => {
      // Scenario: Empty country
      // Given: A driver exists
      const driverId = 'd6';
      const driver = Driver.create({ id: driverId, iracingId: '6', name: 'Empty Country Driver', country: 'US' });
      await driverRepository.create(driver);

      // When: UpdateDriverProfileUseCase.execute() is called with empty country
      const result = await updateDriverProfileUseCase.execute({
        driverId,
        country: '',
      });

      // Then: Should return error
      expect(result.isErr()).toBe(true);
      const error = result.unwrapErr();
      expect(error.code).toBe('INVALID_PROFILE_DATA');
      expect(error.details.message).toBe('Profile data is invalid');
    });
  });

  describe('UpdateDriverProfileUseCase - Error Handling', () => {
    it('should return error when driver does not exist', async () => {
      // Scenario: Non-existent driver
      // Given: No driver exists with the given ID
      const nonExistentDriverId = 'non-existent-driver';

      // When: UpdateDriverProfileUseCase.execute() is called with non-existent driver ID
      const result = await updateDriverProfileUseCase.execute({
        driverId: nonExistentDriverId,
        bio: 'New bio',
      });

      // Then: Should return error
      expect(result.isErr()).toBe(true);
      const error = result.unwrapErr();
      expect(error.code).toBe('DRIVER_NOT_FOUND');
      expect(error.details.message).toContain('Driver with id');
    });

    it('should return error when driver ID is invalid', async () => {
      // Scenario: Invalid driver ID
      // Given: An invalid driver ID (empty string)
      const invalidDriverId = '';

      // When: UpdateDriverProfileUseCase.execute() is called with invalid driver ID
      const result = await updateDriverProfileUseCase.execute({
        driverId: invalidDriverId,
        bio: 'New bio',
      });

      // Then: Should return error
      expect(result.isErr()).toBe(true);
      const error = result.unwrapErr();
      expect(error.code).toBe('DRIVER_NOT_FOUND');
      expect(error.details.message).toContain('Driver with id');
    });
  });

  describe('DriverStatsUseCase - Success Path', () => {
    it('should compute driver statistics from race results', async () => {
      // Scenario: Driver with race results
      // Given: A driver exists
      const driverId = 'd7';
      const driver = Driver.create({ id: driverId, iracingId: '7', name: 'Stats Driver', country: 'US' });
      await driverRepository.create(driver);

      // And: The driver has race results
      await driverStatsRepository.saveDriverStats(driverId, {
        rating: 1800,
        totalRaces: 15,
        wins: 3,
        podiums: 8,
        overallRank: 5,
        safetyRating: 4.2,
        sportsmanshipRating: 90,
        dnfs: 1,
        avgFinish: 4.2,
        bestFinish: 1,
        worstFinish: 12,
        consistency: 80,
        experienceLevel: 'intermediate'
      });

      // When: DriverStatsUseCase.getDriverStats() is called
      const stats = await driverStatsUseCase.getDriverStats(driverId);

      // Then: Should return computed statistics
      expect(stats).not.toBeNull();
      expect(stats!.rating).toBe(1800);
      expect(stats!.totalRaces).toBe(15);
      expect(stats!.wins).toBe(3);
      expect(stats!.podiums).toBe(8);
      expect(stats!.overallRank).toBe(5);
      expect(stats!.safetyRating).toBe(4.2);
      expect(stats!.sportsmanshipRating).toBe(90);
      expect(stats!.dnfs).toBe(1);
      expect(stats!.avgFinish).toBe(4.2);
      expect(stats!.bestFinish).toBe(1);
      expect(stats!.worstFinish).toBe(12);
      expect(stats!.consistency).toBe(80);
      expect(stats!.experienceLevel).toBe('intermediate');
    });

    it('should handle driver with no race results', async () => {
      // Scenario: New driver with no history
      // Given: A driver exists
      const driverId = 'd8';
      const driver = Driver.create({ id: driverId, iracingId: '8', name: 'New Stats Driver', country: 'DE' });
      await driverRepository.create(driver);

      // When: DriverStatsUseCase.getDriverStats() is called
      const stats = await driverStatsUseCase.getDriverStats(driverId);

      // Then: Should return null stats
      expect(stats).toBeNull();
    });
  });

  describe('DriverStatsUseCase - Error Handling', () => {
    it('should return error when driver does not exist', async () => {
      // Scenario: Non-existent driver
      // Given: No driver exists with the given ID
      const nonExistentDriverId = 'non-existent-driver';

      // When: DriverStatsUseCase.getDriverStats() is called
      const stats = await driverStatsUseCase.getDriverStats(nonExistentDriverId);

      // Then: Should return null (no error for non-existent driver)
      expect(stats).toBeNull();
    });
  });

  describe('GetProfileOverviewUseCase - Success Path', () => {
    it('should retrieve complete driver profile overview', async () => {
      // Scenario: Driver with complete data
      // Given: A driver exists
      const driverId = 'd1';
      const driver = Driver.create({ id: driverId, iracingId: '1', name: 'John Doe', country: 'US' });
      await driverRepository.create(driver);

      // And: The driver has statistics
      await driverStatsRepository.saveDriverStats(driverId, {
        rating: 2000,
        totalRaces: 10,
        wins: 2,
        podiums: 5,
        overallRank: 1,
        safetyRating: 4.5,
        sportsmanshipRating: 95,
        dnfs: 0,
        avgFinish: 3.5,
        bestFinish: 1,
        worstFinish: 10,
        consistency: 85,
        experienceLevel: 'pro'
      });

      // And: The driver is in a team
      const team = Team.create({ id: 't1', name: 'Team 1', tag: 'T1', description: 'Desc', ownerId: 'other', leagues: [] });
      await teamRepository.create(team);
      await teamMembershipRepository.saveMembership({
        teamId: 't1',
        driverId: driverId,
        role: 'driver',
        status: 'active',
        joinedAt: new Date()
      });

      // And: The driver has friends
      socialRepository.seed({
        drivers: [driver, Driver.create({ id: 'f1', iracingId: '2', name: 'Friend 1', country: 'UK' })],
        friendships: [{ driverId: driverId, friendId: 'f1' }],
        feedEvents: []
      });

      // When: GetProfileOverviewUseCase.execute() is called
      const result = await getProfileOverviewUseCase.execute({ driverId });

      // Then: The result should contain all profile sections
      expect(result.isOk()).toBe(true);
      const overview = result.unwrap();
      
      expect(overview.driverInfo.driver.id).toBe(driverId);
      expect(overview.stats?.rating).toBe(2000);
      expect(overview.teamMemberships).toHaveLength(1);
      expect(overview.teamMemberships[0].team.id).toBe('t1');
      expect(overview.socialSummary.friendsCount).toBe(1);
      expect(overview.extendedProfile).toBeDefined();
    });

    it('should handle driver with minimal data', async () => {
      // Scenario: New driver with no history
      // Given: A driver exists
      const driverId = 'new';
      const driver = Driver.create({ id: driverId, iracingId: '9', name: 'New Driver', country: 'DE' });
      await driverRepository.create(driver);

      // When: GetProfileOverviewUseCase.execute() is called
      const result = await getProfileOverviewUseCase.execute({ driverId });

      // Then: The result should contain basic info but null stats
      expect(result.isOk()).toBe(true);
      const overview = result.unwrap();
      
      expect(overview.driverInfo.driver.id).toBe(driverId);
      expect(overview.stats).toBeNull();
      expect(overview.teamMemberships).toHaveLength(0);
      expect(overview.socialSummary.friendsCount).toBe(0);
    });
  });

  describe('GetProfileOverviewUseCase - Error Handling', () => {
    it('should return error when driver does not exist', async () => {
      // Scenario: Non-existent driver
      // When: GetProfileOverviewUseCase.execute() is called
      const result = await getProfileOverviewUseCase.execute({ driverId: 'none' });

      // Then: Should return DRIVER_NOT_FOUND
      expect(result.isErr()).toBe(true);
      const error = result.unwrapErr();
      expect(error.code).toBe('DRIVER_NOT_FOUND');
    });
  });
});
