import { describe, expect, it } from 'vitest';

import type { Penalty } from '@core/racing/domain/entities/Penalty';
import { Result } from '@core/racing/domain/entities/result/Result';
import { EventScoringService } from '@core/racing/domain/services/EventScoringService';
import type { BonusRule } from '@core/racing/domain/types/BonusRule';
import type { ChampionshipConfig } from '@core/racing/domain/types/ChampionshipConfig';
import type { SessionType } from '@core/racing/domain/types/SessionType';
import { PointsTable } from '@core/racing/domain/value-objects/PointsTable';

const makeChampionshipConfig = (params: {
  id: string;
  name: string;
  sessionTypes: SessionType[];
  mainPoints: number[];
  sprintPoints?: number[];
  mainBonusRules?: BonusRule[];
}): ChampionshipConfig => {
  const { id, name, sessionTypes, mainPoints, sprintPoints, mainBonusRules } = params;

  const pointsTableBySessionType: Record<SessionType, PointsTable> = {} as Record<SessionType, PointsTable>;

  sessionTypes.forEach((sessionType) => {
    if (sessionType === 'main') {
      pointsTableBySessionType[sessionType] = new PointsTable(
        mainPoints.reduce((acc, points, index) => {
          acc[index + 1] = points;
          return acc;
        }, {} as Record<number, number>),
      );
    } else if (sessionType === 'sprint' && sprintPoints) {
      pointsTableBySessionType[sessionType] = new PointsTable(
        sprintPoints.reduce((acc, points, index) => {
          acc[index + 1] = points;
          return acc;
        }, {} as Record<number, number>),
      );
    } else {
      pointsTableBySessionType[sessionType] = new PointsTable({});
    }
  });

  const bonusRulesBySessionType: Record<SessionType, BonusRule[]> = {} as Record<SessionType, BonusRule[]>;
  sessionTypes.forEach((sessionType) => {
    if (sessionType === 'main' && mainBonusRules) {
      bonusRulesBySessionType[sessionType] = mainBonusRules;
    } else {
      bonusRulesBySessionType[sessionType] = [];
    }
  });

  return {
    id,
    name,
    type: 'driver',
    sessionTypes,
    pointsTableBySessionType,
    bonusRulesBySessionType,
    dropScorePolicy: {
      strategy: 'none',
    },
  };
};

describe('EventScoringService', () => {
  const seasonId = 'season-1';

  it('assigns base points based on finishing positions for a main race', () => {
    const service = new EventScoringService();

    const championship = makeChampionshipConfig({
      id: 'champ-1',
      name: 'Driver Championship',
      sessionTypes: ['main'],
      mainPoints: [25, 18, 15, 12, 10],
    });

    const results: Result[] = [
      Result.create({
        id: 'result-1',
        raceId: 'race-1',
        driverId: 'driver-1',
        position: 1,
        fastestLap: 90000,
        incidents: 0,
        startPosition: 1,
      }),
      Result.create({
        id: 'result-2',
        raceId: 'race-1',
        driverId: 'driver-2',
        position: 2,
        fastestLap: 90500,
        incidents: 0,
        startPosition: 2,
      }),
      Result.create({
        id: 'result-3',
        raceId: 'race-1',
        driverId: 'driver-3',
        position: 3,
        fastestLap: 91000,
        incidents: 0,
        startPosition: 3,
      }),
      Result.create({
        id: 'result-4',
        raceId: 'race-1',
        driverId: 'driver-4',
        position: 4,
        fastestLap: 91500,
        incidents: 0,
        startPosition: 4,
      }),
      Result.create({
        id: 'result-5',
        raceId: 'race-1',
        driverId: 'driver-5',
        position: 5,
        fastestLap: 92000,
        incidents: 0,
        startPosition: 5,
      }),
    ];

    const penalties: Penalty[] = [];

    const points = service.scoreSession({
      seasonId,
      championship,
      sessionType: 'main',
      results,
      penalties,
    });

    const byParticipant = new Map(points.map((p) => [p.participant.id, p]));

    expect(byParticipant.get('driver-1')?.basePoints).toBe(25);
    expect(byParticipant.get('driver-2')?.basePoints).toBe(18);
    expect(byParticipant.get('driver-3')?.basePoints).toBe(15);
    expect(byParticipant.get('driver-4')?.basePoints).toBe(12);
    expect(byParticipant.get('driver-5')?.basePoints).toBe(10);

    for (const entry of byParticipant.values()) {
      expect(entry.bonusPoints).toBe(0);
      expect(entry.penaltyPoints).toBe(0);
      expect(entry.totalPoints).toBe(entry.basePoints);
    }
  });

  it('applies fastest lap bonus only when inside top 10', () => {
    const service = new EventScoringService();

    const fastestLapBonus: BonusRule = {
      id: 'bonus-fastest-lap',
      type: 'fastestLap',
      points: 1,
      requiresFinishInTopN: 10,
    };

    const championship = makeChampionshipConfig({
      id: 'champ-1',
      name: 'Driver Championship',
      sessionTypes: ['main'],
      mainPoints: [25, 18, 15, 12, 10, 8, 6, 4, 2, 1],
      mainBonusRules: [fastestLapBonus],
    });

    const baseResultTemplate = {
      raceId: 'race-1',
      incidents: 0,
    } as const;

    const resultsP11Fastest: Result[] = [
      Result.create({
        id: 'result-1',
        ...baseResultTemplate,
        driverId: 'driver-1',
        position: 1,
        startPosition: 1,
        fastestLap: 91000,
      }),
      Result.create({
        id: 'result-2',
        ...baseResultTemplate,
        driverId: 'driver-2',
        position: 2,
        startPosition: 2,
        fastestLap: 90500,
      }),
      Result.create({
        id: 'result-3',
        ...baseResultTemplate,
        driverId: 'driver-3',
        position: 11,
        startPosition: 15,
        fastestLap: 90000,
      }),
    ];

    const penalties: Penalty[] = [];

    const pointsNoBonus = service.scoreSession({
      seasonId,
      championship,
      sessionType: 'main',
      results: resultsP11Fastest,
      penalties,
    });

    const mapNoBonus = new Map(pointsNoBonus.map((p) => [p.participant.id, p]));

    expect(mapNoBonus.get('driver-3')?.bonusPoints).toBe(0);

    const resultsP8Fastest: Result[] = [
      Result.create({
        id: 'result-1',
        ...baseResultTemplate,
        driverId: 'driver-1',
        position: 1,
        startPosition: 1,
        fastestLap: 91000,
      }),
      Result.create({
        id: 'result-2',
        ...baseResultTemplate,
        driverId: 'driver-2',
        position: 2,
        startPosition: 2,
        fastestLap: 90500,
      }),
      Result.create({
        id: 'result-3',
        ...baseResultTemplate,
        driverId: 'driver-3',
        position: 8,
        startPosition: 15,
        fastestLap: 90000,
      }),
    ];

    const pointsWithBonus = service.scoreSession({
      seasonId,
      championship,
      sessionType: 'main',
      results: resultsP8Fastest,
      penalties,
    });

    const mapWithBonus = new Map(pointsWithBonus.map((p) => [p.participant.id, p]));

    expect(mapWithBonus.get('driver-3')?.bonusPoints).toBe(1);
    expect(mapWithBonus.get('driver-3')?.totalPoints).toBe(
      (mapWithBonus.get('driver-3')?.basePoints || 0) +
        (mapWithBonus.get('driver-3')?.bonusPoints || 0) -
        (mapWithBonus.get('driver-3')?.penaltyPoints || 0),
    );
  });
});
