import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Driver } from '../../domain/entities/Driver';
import type { League } from '../../domain/entities/League';
import type { Race } from '../../domain/entities/Race';
import type { Penalty } from '../../domain/entities/penalty/Penalty';
import type { Result as DomainResult } from '../../domain/entities/result/Result';
import type { DriverRepository } from '../../domain/repositories/DriverRepository';
import type { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import type { PenaltyRepository } from '../../domain/repositories/PenaltyRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { ResultRepository } from '../../domain/repositories/ResultRepository';

export type GetRaceResultsDetailInput = {
  raceId: string;
  driverId?: string;
};

export type GetRaceResultsDetailErrorCode =
  | 'RACE_NOT_FOUND'
  | 'REPOSITORY_ERROR';

export type GetRaceResultsDetailResult = {
  race: Race;
  league: League | null;
  results: DomainResult[];
  drivers: Driver[];
  penalties: Penalty[];
  pointsSystem?: Record<number, number>;
  fastestLapTime?: number;
  currentDriverId?: string;
};

export class GetRaceResultsDetailUseCase {
  constructor(
    private readonly raceRepository: RaceRepository,
    private readonly leagueRepository: LeagueRepository,
    private readonly resultRepository: ResultRepository,
    private readonly driverRepository: DriverRepository,
    private readonly penaltyRepository: PenaltyRepository,
  ) {}

  async execute(
    params: GetRaceResultsDetailInput,
  ): Promise<
    Result<GetRaceResultsDetailResult, ApplicationErrorCode<GetRaceResultsDetailErrorCode, { message: string }>>
  > {
    try {
      const { raceId, driverId } = params;

      const race = await this.raceRepository.findById(raceId);

      if (!race) {
        return Result.err({
          code: 'RACE_NOT_FOUND',
          details: { message: 'Race not found' },
        });
      }

      const [league, results, drivers, penalties] = await Promise.all([
        this.leagueRepository.findById(race.leagueId),
        this.resultRepository.findByRaceId(raceId),
        this.driverRepository.findAll(),
        this.penaltyRepository.findByRaceId(raceId),
      ]);

      const effectiveCurrentDriverId =
        driverId ?? (drivers.length > 0 ? drivers[0]!.id : undefined);

      const pointsSystem = this.buildPointsSystem(league);
      const fastestLapTime = this.getFastestLapTime(results);

      const result: GetRaceResultsDetailResult = {
        race,
        league,
        results,
        drivers,
        penalties,
        ...(pointsSystem ? { pointsSystem } : {}),
        ...(fastestLapTime !== undefined ? { fastestLapTime } : {}),
        ...(effectiveCurrentDriverId ? { currentDriverId: effectiveCurrentDriverId } : {}),
      };

      return Result.ok(result);
    } catch (error: unknown) {
      const message =
        error instanceof Error && typeof error.message === 'string'
          ? error.message
          : 'Failed to load race results detail';

      return Result.err({
        code: 'REPOSITORY_ERROR',
        details: { message },
      });
    }
  }

  private buildPointsSystem(league: League | null): Record<number, number> | undefined {
    if (!league) return undefined;

    const pointsSystems: Record<string, Record<number, number>> = {
      'f1-2024': {
        1: 25,
        2: 18,
        3: 15,
        4: 12,
        5: 10,
        6: 8,
        7: 6,
        8: 4,
        9: 2,
        10: 1,
      },
      indycar: {
        1: 50,
        2: 40,
        3: 35,
        4: 32,
        5: 30,
        6: 28,
        7: 26,
        8: 24,
        9: 22,
        10: 20,
        11: 19,
        12: 18,
        13: 17,
        14: 16,
        15: 15,
      },
    };

    const customPoints = league.settings.customPoints;
    if (customPoints) {
      return customPoints;
    }

    const preset = pointsSystems[league.settings.pointsSystem];
    if (preset) {
      return preset;
    }

    return pointsSystems['f1-2024'];
  }

  private getFastestLapTime(results: DomainResult[]): number | undefined {
    if (results.length === 0) return undefined;
    return Math.min(...results.map(r => r.fastestLap.toNumber()));
  }
}