import type { ISeasonRepository } from '@core/racing/domain/repositories/ISeasonRepository';
import type { ILeagueScoringConfigRepository } from '@core/racing/domain/repositories/ILeagueScoringConfigRepository';
import type { IRaceRepository } from '@core/racing/domain/repositories/IRaceRepository';
import type { IResultRepository } from '@core/racing/domain/repositories/IResultRepository';
import type { IPenaltyRepository } from '@core/racing/domain/repositories/IPenaltyRepository';
import type { IChampionshipStandingRepository } from '@core/racing/domain/repositories/IChampionshipStandingRepository';
import type { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository';

import type { ChampionshipConfig } from '@core/racing/domain/types/ChampionshipConfig';
import type { SessionType } from '@core/racing/domain/types/SessionType';
import type { ChampionshipStanding } from '@core/racing/domain/entities/championship/ChampionshipStanding';
import { EventScoringService } from '@core/racing/domain/services/EventScoringService';
import { ChampionshipAggregator } from '@core/racing/domain/services/ChampionshipAggregator';

import type { UseCaseOutputPort, Logger } from '@core/shared/application';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';

export type RecalculateChampionshipStandingsInput = {
  leagueId: string;
  seasonId: string;
};

export type ChampionshipStandingsEntry = {
  driverId: string | null;
  teamId: string | null;
  position: number;
  points: number;
};

export type RecalculateChampionshipStandingsResult = {
  leagueId: string;
  seasonId: string;
  entries: ChampionshipStandingsEntry[];
};

export type RecalculateChampionshipStandingsErrorCode =
  | 'LEAGUE_NOT_FOUND'
  | 'SEASON_NOT_FOUND'
  | 'REPOSITORY_ERROR';

export class RecalculateChampionshipStandingsUseCase {
  constructor(
    private readonly leagueRepository: ILeagueRepository,
    private readonly seasonRepository: ISeasonRepository,
    private readonly leagueScoringConfigRepository: ILeagueScoringConfigRepository,
    private readonly raceRepository: IRaceRepository,
    private readonly resultRepository: IResultRepository,
    private readonly penaltyRepository: IPenaltyRepository,
    private readonly championshipStandingRepository: IChampionshipStandingRepository,
    private readonly eventScoringService: EventScoringService,
    private readonly championshipAggregator: ChampionshipAggregator,
    private readonly logger: Logger,
    private readonly output: UseCaseOutputPort<RecalculateChampionshipStandingsResult>,
  ) {}

  async execute(
    input: RecalculateChampionshipStandingsInput,
  ): Promise<
    Result<
      void,
      ApplicationErrorCode<RecalculateChampionshipStandingsErrorCode, { message: string }>
    >
  > {
    const { leagueId, seasonId } = input;

    try {
      const league = await this.leagueRepository.findById(leagueId);
      if (!league) {
        return Result.err({
          code: 'LEAGUE_NOT_FOUND',
          details: { message: `League not found: ${leagueId}` },
        });
      }

      const season = await this.seasonRepository.findById(seasonId);
      if (!season || season.leagueId !== leagueId) {
        return Result.err({
          code: 'SEASON_NOT_FOUND',
          details: {
            message: `Season not found for league: leagueId=${leagueId}, seasonId=${seasonId}`,
          },
        });
      }

      const leagueScoringConfig =
        await this.leagueScoringConfigRepository.findBySeasonId(seasonId);
      if (!leagueScoringConfig) {
        throw new Error(`League scoring config not found for season: ${seasonId}`);
      }

      const championship = this.findChampionshipConfig(leagueScoringConfig.championships);

      const races = await this.raceRepository.findByLeagueId(leagueId);

      const eventPointsByEventId: Record<string, ReturnType<EventScoringService['scoreSession']>> =
        {};

      for (const race of races) {
        const sessionType = this.mapRaceSessionType(String(race.sessionType));
        if (!championship.sessionTypes.includes(sessionType)) {
          continue;
        }

        const results = await this.resultRepository.findByRaceId(race.id);
        const penalties = await this.penaltyRepository.findByRaceId(race.id);

        const participantPoints = this.eventScoringService.scoreSession({
          seasonId,
          championship,
          sessionType,
          results,
          penalties,
        });

        eventPointsByEventId[race.id] = participantPoints;
      }

      const standings: ChampionshipStanding[] = this.championshipAggregator.aggregate({
        seasonId,
        championship,
        eventPointsByEventId,
      });

      await this.championshipStandingRepository.saveAll(standings);

      const result: RecalculateChampionshipStandingsResult = {
        leagueId,
        seasonId,
        entries: standings.map((standing) => ({
          driverId: standing.participant?.id ?? null,
          teamId: null,
          position: standing.position.toNumber(),
          points: standing.totalPoints.toNumber(),
        })),
      };

      this.output.present(result);

      return Result.ok(undefined);
    } catch (error) {
      const err = error as Error;
      this.logger.error('Failed to recalculate championship standings', err, {
        leagueId,
        seasonId,
      });

      return Result.err({
        code: 'REPOSITORY_ERROR',
        details: {
          message:
            err.message || 'Failed to recalculate championship standings',
        },
      });
    }
  }

  private findChampionshipConfig(championships: ChampionshipConfig[]): ChampionshipConfig {
    if (!championships || championships.length === 0) {
      throw new Error('No championship configurations found');
    }

    return championships[0]!;
  }

  private mapRaceSessionType(sessionType: SessionType | string): SessionType {
    if (sessionType === 'race') {
      return 'main';
    }
    if (
      sessionType === 'practice' ||
      sessionType === 'qualifying' ||
      sessionType === 'timeTrial'
    ) {
      return sessionType;
    }
    return 'main';
  }
}
