import { RecordRaceRatingEventsUseCase } from './RecordRaceRatingEventsUseCase';
import { IRaceResultsProvider, RaceResultsData } from '../ports/IRaceResultsProvider';
import { IRatingEventRepository } from '../../domain/repositories/IRatingEventRepository';
import { IUserRatingRepository } from '../../domain/repositories/IUserRatingRepository';
import { AppendRatingEventsUseCase } from './AppendRatingEventsUseCase';
import { UserRating } from '../../domain/value-objects/UserRating';
import { RatingEvent } from '../../domain/entities/RatingEvent';
import { RatingEventId } from '../../domain/value-objects/RatingEventId';
import { RatingDimensionKey } from '../../domain/value-objects/RatingDimensionKey';
import { RatingDelta } from '../../domain/value-objects/RatingDelta';

// Mock implementations
class MockRaceResultsProvider implements IRaceResultsProvider {
  private results: RaceResultsData | null = null;

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

  async getRaceResults(raceId: string): Promise<RaceResultsData | null> {
    return this.results;
  }

  async hasRaceResults(raceId: string): Promise<boolean> {
    return this.results !== null;
  }
}

class MockRatingEventRepository implements IRatingEventRepository {
  private events: RatingEvent[] = [];

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

  async findByUserId(userId: string): Promise<RatingEvent[]> {
    return this.events.filter(e => e.userId === userId);
  }

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

  async getAllByUserId(userId: string): Promise<RatingEvent[]> {
    return this.events.filter(e => e.userId === userId);
  }

  async findEventsPaginated(userId: string, options?: import('@core/identity/domain/repositories/IRatingEventRepository').PaginatedQueryOptions): Promise<import('@core/identity/domain/repositories/IRatingEventRepository').PaginatedResult<RatingEvent>> {
    const allEvents = await this.findByUserId(userId);
    
    // Apply filters
    let filtered = allEvents;
    if (options?.filter) {
      const filter = options.filter;
      if (filter.dimensions) {
        filtered = filtered.filter(e => filter.dimensions!.includes(e.dimension.value));
      }
      if (filter.sourceTypes) {
        filtered = filtered.filter(e => filter.sourceTypes!.includes(e.source.type));
      }
      if (filter.from) {
        filtered = filtered.filter(e => e.occurredAt >= filter.from!);
      }
      if (filter.to) {
        filtered = filtered.filter(e => e.occurredAt <= filter.to!);
      }
      if (filter.reasonCodes) {
        filtered = filtered.filter(e => filter.reasonCodes!.includes(e.reason.code));
      }
      if (filter.visibility) {
        filtered = filtered.filter(e => e.visibility.public === (filter.visibility === 'public'));
      }
    }

    const total = filtered.length;
    const limit = options?.limit ?? 10;
    const offset = options?.offset ?? 0;
    const items = filtered.slice(offset, offset + limit);
    const hasMore = offset + limit < total;
    const nextOffset = hasMore ? offset + limit : undefined;

    const result: import('@core/identity/domain/repositories/IRatingEventRepository').PaginatedResult<RatingEvent> = {
      items,
      total,
      limit,
      offset,
      hasMore
    };
    
    if (nextOffset !== undefined) {
      result.nextOffset = nextOffset;
    }

    return result;
  }
}

class MockUserRatingRepository implements IUserRatingRepository {
  private ratings: Map<string, UserRating> = new Map();

  async findByUserId(userId: string): Promise<UserRating | null> {
    return this.ratings.get(userId) || null;
  }

  async save(userRating: UserRating): Promise<UserRating> {
    this.ratings.set(userRating.userId, userRating);
    return userRating;
  }

  // Helper for tests
  setRating(userId: string, rating: UserRating) {
    this.ratings.set(userId, rating);
  }
}

describe('RecordRaceRatingEventsUseCase', () => {
  let useCase: RecordRaceRatingEventsUseCase;
  let mockRaceResultsProvider: MockRaceResultsProvider;
  let mockRatingEventRepository: MockRatingEventRepository;
  let mockUserRatingRepository: MockUserRatingRepository;
  let appendRatingEventsUseCase: AppendRatingEventsUseCase;

  beforeEach(() => {
    mockRaceResultsProvider = new MockRaceResultsProvider();
    mockRatingEventRepository = new MockRatingEventRepository();
    mockUserRatingRepository = new MockUserRatingRepository();
    appendRatingEventsUseCase = new AppendRatingEventsUseCase(
      mockRatingEventRepository,
      mockUserRatingRepository
    );
    useCase = new RecordRaceRatingEventsUseCase(
      mockRaceResultsProvider,
      mockRatingEventRepository,
      mockUserRatingRepository,
      appendRatingEventsUseCase
    );
  });

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

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

      expect(result.success).toBe(false);
      expect(result.raceId).toBe('race-123');
      expect(result.eventsCreated).toBe(0);
      expect(result.driversUpdated).toEqual([]);
      expect(result.errors).toContain('Race results not found');
    });

    it('should return error when no results in race', async () => {
      mockRaceResultsProvider.setResults({
        raceId: 'race-123',
        results: [],
      });

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

      expect(result.success).toBe(false);
      expect(result.eventsCreated).toBe(0);
      expect(result.errors).toContain('No results found for race');
    });

    it('should process single driver with good performance', async () => {
      mockRaceResultsProvider.setResults({
        raceId: 'race-123',
        results: [
          {
            userId: 'user-123',
            startPos: 5,
            finishPos: 2,
            incidents: 0,
            status: 'finished',
            sof: 2500,
          },
        ],
      });

      // Set initial rating for user
      const initialRating = UserRating.create('user-123');
      mockUserRatingRepository.setRating('user-123', initialRating);

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

      expect(result.success).toBe(true);
      expect(result.raceId).toBe('race-123');
      expect(result.eventsCreated).toBeGreaterThan(0);
      expect(result.driversUpdated).toContain('user-123');
      expect(result.errors).toEqual([]);
    });

    it('should process multiple drivers with mixed results', async () => {
      mockRaceResultsProvider.setResults({
        raceId: 'race-123',
        results: [
          {
            userId: 'user-123',
            startPos: 5,
            finishPos: 2,
            incidents: 0,
            status: 'finished',
            sof: 2500,
          },
          {
            userId: 'user-456',
            startPos: 3,
            finishPos: 8,
            incidents: 2,
            status: 'finished',
            sof: 2500,
          },
          {
            userId: 'user-789',
            startPos: 5,
            finishPos: 5,
            incidents: 0,
            status: 'dns',
            sof: 2500,
          },
        ],
      });

      // Set initial ratings
      mockUserRatingRepository.setRating('user-123', UserRating.create('user-123'));
      mockUserRatingRepository.setRating('user-456', UserRating.create('user-456'));
      mockUserRatingRepository.setRating('user-789', UserRating.create('user-789'));

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

      expect(result.success).toBe(true);
      expect(result.eventsCreated).toBeGreaterThan(0);
      expect(result.driversUpdated.length).toBe(3);
      expect(result.driversUpdated).toContain('user-123');
      expect(result.driversUpdated).toContain('user-456');
      expect(result.driversUpdated).toContain('user-789');
      expect(result.errors).toEqual([]);
    });

    it('should compute SoF if not provided', async () => {
      mockRaceResultsProvider.setResults({
        raceId: 'race-123',
        results: [
          {
            userId: 'user-123',
            startPos: 5,
            finishPos: 2,
            incidents: 0,
            status: 'finished',
            // No sof
          },
          {
            userId: 'user-456',
            startPos: 3,
            finishPos: 8,
            incidents: 0,
            status: 'finished',
            // No sof
          },
        ],
      });

      // Set ratings for SoF calculation
      const rating1 = UserRating.create('user-123');
      const rating2 = UserRating.create('user-456');
      // Update driver ratings to specific values
      mockUserRatingRepository.setRating('user-123', rating1.updateDriverRating(60));
      mockUserRatingRepository.setRating('user-456', rating2.updateDriverRating(40));

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

      expect(result.success).toBe(true);
      expect(result.eventsCreated).toBeGreaterThan(0);
      expect(result.driversUpdated.length).toBe(2);
    });

    it('should handle errors for individual drivers gracefully', async () => {
      mockRaceResultsProvider.setResults({
        raceId: 'race-123',
        results: [
          {
            userId: 'user-123',
            startPos: 5,
            finishPos: 2,
            incidents: 0,
            status: 'finished',
            sof: 2500,
          },
          {
            userId: 'user-456',
            startPos: 3,
            finishPos: 8,
            incidents: 0,
            status: 'finished',
            sof: 2500,
          },
        ],
      });

      // Only set rating for first user, second will fail
      mockUserRatingRepository.setRating('user-123', UserRating.create('user-123'));

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

      // Should still succeed overall but with errors
      expect(result.raceId).toBe('race-123');
      expect(result.driversUpdated).toContain('user-123');
      expect(result.errors).toBeDefined();
      expect(result.errors!.length).toBeGreaterThan(0);
    });

    it('should return success with no events when no valid events created', async () => {
      // This would require a scenario where factory creates no events
      // For now, we'll test with empty results
      mockRaceResultsProvider.setResults({
        raceId: 'race-123',
        results: [],
      });

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

      expect(result.success).toBe(false); // No results
    });

    it('should handle repository errors', async () => {
      mockRaceResultsProvider.setResults({
        raceId: 'race-123',
        results: [
          {
            userId: 'user-123',
            startPos: 5,
            finishPos: 2,
            incidents: 0,
            status: 'finished',
            sof: 2500,
          },
        ],
      });

      mockUserRatingRepository.setRating('user-123', UserRating.create('user-123'));

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

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

      expect(result.success).toBe(false);
      expect(result.errors).toBeDefined();
      expect(result.errors!.length).toBeGreaterThan(0);

      // Restore
      mockRatingEventRepository.save = originalSave;
    });
  });
});
