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 { TeamRatingDelta } from '@core/racing/domain/value-objects/TeamRatingDelta';
import { TeamRatingDimensionKey } from '@core/racing/domain/value-objects/TeamRatingDimensionKey';
import { TeamRatingEventId } from '@core/racing/domain/value-objects/TeamRatingEventId';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { TeamRatingSnapshot } from '../../domain/services/TeamRatingSnapshotCalculator';
import { RecomputeTeamRatingSnapshotUseCase } from './RecomputeTeamRatingSnapshotUseCase';

// Mock repositories
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,
    };
  }

  setEvents(events: TeamRatingEvent[]) {
    this.events = events;
  }

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

class MockTeamRatingRepository implements TeamRatingRepository {
  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;
  }

  getSnapshot(teamId: string) {
    return this.snapshots.get(teamId);
  }

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

describe('RecomputeTeamRatingSnapshotUseCase', () => {
  let useCase: RecomputeTeamRatingSnapshotUseCase;
  let mockEventRepo: MockTeamRatingEventRepository;
  let mockRatingRepo: MockTeamRatingRepository;

  beforeEach(() => {
    mockEventRepo = new MockTeamRatingEventRepository();
    mockRatingRepo = new MockTeamRatingRepository();
    useCase = new RecomputeTeamRatingSnapshotUseCase(mockEventRepo, mockRatingRepo);
  });

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

  describe('execute', () => {
    it('should create snapshot with default values when no events exist', async () => {
      await useCase.execute('team-123');

      const snapshot = mockRatingRepo.getSnapshot('team-123');
      expect(snapshot).toBeDefined();
      expect(snapshot.teamId).toBe('team-123');
      expect(snapshot.driving.value).toBe(50);
      expect(snapshot.adminTrust.value).toBe(50);
      expect(snapshot.overall).toBe(50);
      expect(snapshot.eventCount).toBe(0);
    });

    it('should recompute snapshot from single event', async () => {
      const event = TeamRatingEvent.create({
        id: TeamRatingEventId.generate(),
        teamId: 'team-123',
        dimension: TeamRatingDimensionKey.create('driving'),
        delta: TeamRatingDelta.create(10),
        occurredAt: new Date('2024-01-01T10:00:00Z'),
        createdAt: new Date('2024-01-01T10:00:00Z'),
        source: { type: 'race', id: 'race-456' },
        reason: { code: 'RACE_FINISH', description: 'Finished 1st' },
        visibility: { public: true },
        version: 1,
      });

      mockEventRepo.setEvents([event]);

      await useCase.execute('team-123');

      const snapshot = mockRatingRepo.getSnapshot('team-123');
      expect(snapshot).toBeDefined();
      expect(snapshot.teamId).toBe('team-123');
      expect(snapshot.driving.value).toBe(60); // 50 + 10
      expect(snapshot.adminTrust.value).toBe(50); // Default
      expect(snapshot.overall).toBe(57); // 60 * 0.7 + 50 * 0.3 = 57
      expect(snapshot.eventCount).toBe(1);
    });

    it('should recompute snapshot from multiple events', async () => {
      const events = [
        TeamRatingEvent.create({
          id: TeamRatingEventId.generate(),
          teamId: 'team-123',
          dimension: TeamRatingDimensionKey.create('driving'),
          delta: TeamRatingDelta.create(10),
          weight: 1,
          occurredAt: new Date('2024-01-01T10:00:00Z'),
          createdAt: new Date('2024-01-01T10:00:00Z'),
          source: { type: 'race', id: 'race-456' },
          reason: { code: 'RACE_FINISH', description: 'Finished 1st' },
          visibility: { public: true },
          version: 1,
        }),
        TeamRatingEvent.create({
          id: TeamRatingEventId.generate(),
          teamId: 'team-123',
          dimension: TeamRatingDimensionKey.create('driving'),
          delta: TeamRatingDelta.create(5),
          weight: 2,
          occurredAt: new Date('2024-01-01T11:00:00Z'),
          createdAt: new Date('2024-01-01T11:00:00Z'),
          source: { type: 'race', id: 'race-457' },
          reason: { code: 'RACE_FINISH', description: 'Finished 2nd' },
          visibility: { public: true },
          version: 1,
        }),
        TeamRatingEvent.create({
          id: TeamRatingEventId.generate(),
          teamId: 'team-123',
          dimension: TeamRatingDimensionKey.create('adminTrust'),
          delta: TeamRatingDelta.create(3),
          weight: 1,
          occurredAt: new Date('2024-01-01T12:00:00Z'),
          createdAt: new Date('2024-01-01T12:00:00Z'),
          source: { type: 'adminAction', id: 'action-789' },
          reason: { code: 'POSITIVE_ADMIN_ACTION', description: 'Helped organize event' },
          visibility: { public: true },
          version: 1,
        }),
      ];

      mockEventRepo.setEvents(events);

      await useCase.execute('team-123');

      const snapshot = mockRatingRepo.getSnapshot('team-123');
      expect(snapshot).toBeDefined();
      expect(snapshot.teamId).toBe('team-123');
      
      // Driving: weighted average of (10*1 + 5*2) / (1+2) = 20/3 = 6.67, so 50 + 6.67 = 56.67
      expect(snapshot.driving.value).toBeCloseTo(56.67, 1);
      
      // AdminTrust: 50 + 3 = 53
      expect(snapshot.adminTrust.value).toBe(53);
      
      // Overall: 56.67 * 0.7 + 53 * 0.3 = 39.67 + 15.9 = 55.57 ≈ 55.6
      expect(snapshot.overall).toBeCloseTo(55.6, 1);
      
      expect(snapshot.eventCount).toBe(3);
    });

    it('should handle events with different dimensions correctly', async () => {
      const events = [
        TeamRatingEvent.create({
          id: TeamRatingEventId.generate(),
          teamId: 'team-456',
          dimension: TeamRatingDimensionKey.create('adminTrust'),
          delta: TeamRatingDelta.create(15),
          occurredAt: new Date('2024-01-01T10:00:00Z'),
          createdAt: new Date('2024-01-01T10:00:00Z'),
          source: { type: 'adminAction', id: 'action-123' },
          reason: { code: 'EXCELLENT_ADMIN', description: 'Great leadership' },
          visibility: { public: true },
          version: 1,
        }),
      ];

      mockEventRepo.setEvents(events);

      await useCase.execute('team-456');

      const snapshot = mockRatingRepo.getSnapshot('team-456');
      expect(snapshot).toBeDefined();
      expect(snapshot.adminTrust.value).toBe(65); // 50 + 15
      expect(snapshot.driving.value).toBe(50); // Default
      expect(snapshot.overall).toBe(54.5); // 50 * 0.7 + 65 * 0.3 = 54.5
    });

    it('should overwrite existing snapshot with recomputed values', async () => {
      // First, create an initial snapshot
      const initialEvent = TeamRatingEvent.create({
        id: TeamRatingEventId.generate(),
        teamId: 'team-123',
        dimension: TeamRatingDimensionKey.create('driving'),
        delta: TeamRatingDelta.create(5),
        occurredAt: new Date('2024-01-01T10:00:00Z'),
        createdAt: new Date('2024-01-01T10:00:00Z'),
        source: { type: 'race', id: 'race-456' },
        reason: { code: 'RACE_FINISH', description: 'Finished 1st' },
        visibility: { public: true },
        version: 1,
      });

      mockEventRepo.setEvents([initialEvent]);
      await useCase.execute('team-123');

      let snapshot = mockRatingRepo.getSnapshot('team-123');
      expect(snapshot.driving.value).toBe(55);

      // Now add more events and recompute
      const additionalEvent = TeamRatingEvent.create({
        id: TeamRatingEventId.generate(),
        teamId: 'team-123',
        dimension: TeamRatingDimensionKey.create('driving'),
        delta: TeamRatingDelta.create(10),
        occurredAt: new Date('2024-01-01T11:00:00Z'),
        createdAt: new Date('2024-01-01T11:00:00Z'),
        source: { type: 'race', id: 'race-457' },
        reason: { code: 'RACE_FINISH', description: 'Finished 2nd' },
        visibility: { public: true },
        version: 1,
      });

      mockEventRepo.setEvents([initialEvent, additionalEvent]);
      await useCase.execute('team-123');

      snapshot = mockRatingRepo.getSnapshot('team-123');
      expect(snapshot.driving.value).toBe(57.5); // Weighted average: (5 + 10) / 2 = 7.5, so 50 + 7.5 = 57.5
      expect(snapshot.eventCount).toBe(2);
    });
  });
});