import { TeamRatingEvent } from '@core/racing/domain/entities/TeamRatingEvent';
import { TeamRatingEventRepository, PaginatedResult } from '@core/racing/domain/repositories/TeamRatingEventRepository';
import { TeamRatingRepository } from '@core/racing/domain/repositories/TeamRatingRepository';
import { TeamDrivingRaceFactsDto } from '@core/racing/domain/services/TeamDrivingRatingEventFactory';
import { TeamRatingEventId } from '@core/racing/domain/value-objects/TeamRatingEventId';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { TeamRating } from '../../domain/value-objects/TeamRating';
import { TeamRaceResultsProvider } from '../ports/TeamRaceResultsProvider';
import { AppendTeamRatingEventsUseCase } from './AppendTeamRatingEventsUseCase';
import { RecordTeamRaceRatingEventsUseCase } from './RecordTeamRaceRatingEventsUseCase';

// Mock repositories
class MockTeamRaceResultsProvider implements TeamRaceResultsProvider {
  private results: TeamDrivingRaceFactsDto | null = null;

  async getTeamRaceResults(): Promise<TeamDrivingRaceFactsDto | null> {
    return this.results;
  }

  setResults(results: TeamDrivingRaceFactsDto | null) {
    this.results = results;
  }
}

class MockTeamRatingEventRepository implements TeamRatingEventRepository {
  private events: TeamRatingEvent[] = [];

  async save(event: TeamRatingEvent): Promise<TeamRatingEvent> {
    this.events.push(event);
    return event;
  }

  async findByTeamId(teamId: string): Promise<TeamRatingEvent[]> {
    return this.events.filter(e => e.teamId === teamId);
  }

  async findByIds(ids: TeamRatingEventId[]): Promise<TeamRatingEvent[]> {
    return this.events.filter(e => ids.some(id => id.equals(e.id)));
  }

  async getAllByTeamId(teamId: string): Promise<TeamRatingEvent[]> {
    return this.events.filter(e => e.teamId === teamId);
  }

  async findEventsPaginated(teamId: string): Promise<PaginatedResult<TeamRatingEvent>> {
    const events = await this.getAllByTeamId(teamId);
    return {
      items: events,
      total: events.length,
      limit: 10,
      offset: 0,
      hasMore: false,
    };
  }

  clear() {
    this.events = [];
  }
}

class MockTeamRatingRepository implements TeamRatingRepository {
  private snapshots: Map<string, TeamRating> = new Map();

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

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

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

describe('RecordTeamRaceRatingEventsUseCase', () => {
  let useCase: RecordTeamRaceRatingEventsUseCase;
  let mockResultsProvider: MockTeamRaceResultsProvider;
  let mockEventRepo: MockTeamRatingEventRepository;
  let mockRatingRepo: MockTeamRatingRepository;
  let appendUseCase: AppendTeamRatingEventsUseCase;

  beforeEach(() => {
    mockResultsProvider = new MockTeamRaceResultsProvider();
    mockEventRepo = new MockTeamRatingEventRepository();
    mockRatingRepo = new MockTeamRatingRepository();
    appendUseCase = new AppendTeamRatingEventsUseCase(mockEventRepo, mockRatingRepo);
    useCase = new RecordTeamRaceRatingEventsUseCase(
      mockResultsProvider,
      appendUseCase
    );
  });

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

  describe('execute', () => {
    it('should return error when race results not found', async () => {
      mockResultsProvider.setResults(null);

      const result = await useCase.execute({ raceId: 'race-123' });

      expect(result.success).toBe(false);
      expect(result.errors).toContain('Team race results not found');
      expect(result.eventsCreated).toBe(0);
      expect(result.teamsUpdated).toEqual([]);
    });

    it('should return success with no events when results are empty', async () => {
      mockResultsProvider.setResults({
        raceId: 'race-123',
        teamId: 'team-123',
        results: [],
      });

      const result = await useCase.execute({ raceId: 'race-123' });

      expect(result.success).toBe(true);
      expect(result.eventsCreated).toBe(0);
      expect(result.teamsUpdated).toEqual([]);
      expect(result.errors).toEqual([]);
    });

    it('should create events for single team and update snapshot', async () => {
      const raceResults: TeamDrivingRaceFactsDto = {
        raceId: 'race-123',
        teamId: 'team-123',
        results: [
          {
            teamId: 'team-123',
            position: 1,
            incidents: 0,
            status: 'finished',
            fieldSize: 3,
            strengthOfField: 55,
          },
        ],
      };

      mockResultsProvider.setResults(raceResults);

      const result = await useCase.execute({ raceId: 'race-123' });

      expect(result.success).toBe(true);
      expect(result.eventsCreated).toBeGreaterThan(0);
      expect(result.teamsUpdated).toContain('team-123');
      expect(result.errors).toEqual([]);

      // Verify events were saved
      const savedEvents = await mockEventRepo.getAllByTeamId('team-123');
      expect(savedEvents.length).toBeGreaterThan(0);

      // Verify snapshot was updated
      const snapshot = await mockRatingRepo.findByTeamId('team-123');
      expect(snapshot).toBeDefined();
      expect(snapshot.teamId).toBe('team-123');
    });

    it('should create events for multiple teams', async () => {
      const raceResults: TeamDrivingRaceFactsDto = {
        raceId: 'race-123',
        teamId: 'team-123',
        results: [
          {
            teamId: 'team-123',
            position: 1,
            incidents: 0,
            status: 'finished',
            fieldSize: 3,
            strengthOfField: 55,
          },
          {
            teamId: 'team-456',
            position: 2,
            incidents: 1,
            status: 'finished',
            fieldSize: 3,
            strengthOfField: 55,
          },
          {
            teamId: 'team-789',
            position: 3,
            incidents: 0,
            status: 'dnf',
            fieldSize: 3,
            strengthOfField: 55,
          },
        ],
      };

      mockResultsProvider.setResults(raceResults);

      const result = await useCase.execute({ raceId: 'race-123' });

      expect(result.success).toBe(true);
      expect(result.eventsCreated).toBeGreaterThan(0);
      expect(result.teamsUpdated).toContain('team-123');
      expect(result.teamsUpdated).toContain('team-456');
      expect(result.teamsUpdated).toContain('team-789');
      expect(result.errors).toEqual([]);

      // Verify all team snapshots were updated
      const snapshot1 = await mockRatingRepo.findByTeamId('team-123');
      const snapshot2 = await mockRatingRepo.findByTeamId('team-456');
      const snapshot3 = await mockRatingRepo.findByTeamId('team-789');

      expect(snapshot1).toBeDefined();
      expect(snapshot2).toBeDefined();
      expect(snapshot3).toBeDefined();
    });

    it('should handle optional ratings in results', async () => {
      const raceResults: TeamDrivingRaceFactsDto = {
        raceId: 'race-123',
        teamId: 'team-123',
        results: [
          {
            teamId: 'team-123',
            position: 1,
            incidents: 0,
            status: 'finished',
            fieldSize: 3,
            strengthOfField: 65,
            pace: 85,
            consistency: 80,
            teamwork: 90,
            sportsmanship: 95,
          },
        ],
      };

      mockResultsProvider.setResults(raceResults);

      const result = await useCase.execute({ raceId: 'race-123' });

      expect(result.success).toBe(true);
      expect(result.eventsCreated).toBeGreaterThan(5); // Should have many events
      expect(result.teamsUpdated).toContain('team-123');

      // Verify events include optional rating events
      const savedEvents = await mockEventRepo.getAllByTeamId('team-123');
      const paceEvent = savedEvents.find(e => e.reason.code === 'RACE_PACE');
      const consistencyEvent = savedEvents.find(e => e.reason.code === 'RACE_CONSISTENCY');
      const teamworkEvent = savedEvents.find(e => e.reason.code === 'RACE_TEAMWORK');
      const sportsmanshipEvent = savedEvents.find(e => e.reason.code === 'RACE_SPORTSMANSHIP');

      expect(paceEvent).toBeDefined();
      expect(consistencyEvent).toBeDefined();
      expect(teamworkEvent).toBeDefined();
      expect(sportsmanshipEvent).toBeDefined();
    });

    it('should handle partial failures gracefully', async () => {
      const raceResults: TeamDrivingRaceFactsDto = {
        raceId: 'race-123',
        teamId: 'team-123',
        results: [
          {
            teamId: 'team-123',
            position: 1,
            incidents: 0,
            status: 'finished',
            fieldSize: 2,
            strengthOfField: 55,
          },
          {
            teamId: 'team-456',
            position: 2,
            incidents: 0,
            status: 'finished',
            fieldSize: 2,
            strengthOfField: 55,
          },
        ],
      };

      mockResultsProvider.setResults(raceResults);

      // Mock the append use case to fail for team-456
      const originalExecute = appendUseCase.execute.bind(appendUseCase);
      appendUseCase.execute = async (events) => {
        if (events.length > 0 && events[0] && events[0].teamId === 'team-456') {
          throw new Error('Simulated failure for team-456');
        }
        return originalExecute(events);
      };

      const result = await useCase.execute({ raceId: 'race-123' });

      expect(result.success).toBe(false);
      expect(result.teamsUpdated).toContain('team-123');
      expect(result.teamsUpdated).not.toContain('team-456');
      expect(result.errors.length).toBeGreaterThan(0);
      expect(result.errors[0]).toContain('team-456');
    });

    it('should handle repository errors', async () => {
      const raceResults: TeamDrivingRaceFactsDto = {
        raceId: 'race-123',
        teamId: 'team-123',
        results: [
          {
            teamId: 'team-123',
            position: 1,
            incidents: 0,
            status: 'finished',
            fieldSize: 1,
            strengthOfField: 55,
          },
        ],
      };

      mockResultsProvider.setResults(raceResults);

      // Mock repository to throw error
      mockEventRepo.save = async () => {
        throw new Error('Repository error');
      };

      const result = await useCase.execute({ raceId: 'race-123' });

      expect(result.success).toBe(false);
      expect(result.errors.length).toBeGreaterThan(0);
      expect(result.errors[0]).toContain('Repository error');
      expect(result.eventsCreated).toBe(0);
      expect(result.teamsUpdated).toEqual([]);
    });

    it('should handle empty results array', async () => {
      mockResultsProvider.setResults({
        raceId: 'race-123',
        teamId: 'team-123',
        results: [],
      });

      const result = await useCase.execute({ raceId: 'race-123' });

      expect(result.success).toBe(true);
      expect(result.eventsCreated).toBe(0);
      expect(result.teamsUpdated).toEqual([]);
      expect(result.errors).toEqual([]);
    });

    it('should handle race with minimal events generated', async () => {
      // Race where teams have some impact (DNS creates penalty event)
      const raceResults: TeamDrivingRaceFactsDto = {
        raceId: 'race-123',
        teamId: 'team-123',
        results: [
          {
            teamId: 'team-123',
            position: 1,
            incidents: 0,
            status: 'dns',
            fieldSize: 1,
            strengthOfField: 50,
          },
        ],
      };

      mockResultsProvider.setResults(raceResults);

      const result = await useCase.execute({ raceId: 'race-123' });

      expect(result.success).toBe(true);
      expect(result.eventsCreated).toBeGreaterThan(0); // DNS creates penalty event
      expect(result.teamsUpdated).toContain('team-123');
      expect(result.errors).toEqual([]);
    });
  });
});