import { Inject, Injectable, NotFoundException } from '@nestjs/common';

// DTOs
import { GetRaceDetailParamsDTO } from './dtos/GetRaceDetailParamsDTO';
import { ImportRaceResultsDTO } from './dtos/ImportRaceResultsDTO';
import { RaceActionParamsDTO } from './dtos/RaceActionParamsDTO';
import { RegisterForRaceParamsDTO } from './dtos/RegisterForRaceParamsDTO';
import { WithdrawFromRaceParamsDTO } from './dtos/WithdrawFromRaceParamsDTO';

// Core imports
import type { Logger } from '@core/shared/domain/Logger';

// Use cases
import { CancelRaceUseCase } from '@core/racing/application/use-cases/CancelRaceUseCase';
import { CompleteRaceUseCase } from '@core/racing/application/use-cases/CompleteRaceUseCase';
import { FileProtestUseCase } from '@core/racing/application/use-cases/FileProtestUseCase';
import { GetAllRacesPageDataUseCase } from '@core/racing/application/use-cases/GetAllRacesPageDataUseCase';
import { GetAllRacesUseCase } from '@core/racing/application/use-cases/GetAllRacesUseCase';
import { GetRaceDetailUseCase } from '@core/racing/application/use-cases/GetRaceDetailUseCase';
import { GetRacePenaltiesUseCase } from '@core/racing/application/use-cases/GetRacePenaltiesUseCase';
import { GetRaceProtestsUseCase } from '@core/racing/application/use-cases/GetRaceProtestsUseCase';
import { GetRaceResultsDetailUseCase } from '@core/racing/application/use-cases/GetRaceResultsDetailUseCase';
import { GetRacesPageDataUseCase } from '@core/racing/application/use-cases/GetRacesPageDataUseCase';
import { GetRaceWithSOFUseCase } from '@core/racing/application/use-cases/GetRaceWithSOFUseCase';
import { GetTotalRacesUseCase } from '@core/racing/application/use-cases/GetTotalRacesUseCase';
import { ImportRaceResultsApiUseCase } from '@core/racing/application/use-cases/ImportRaceResultsApiUseCase';
import { QuickPenaltyUseCase } from '@core/racing/application/use-cases/QuickPenaltyUseCase';
import { RegisterForRaceUseCase } from '@core/racing/application/use-cases/RegisterForRaceUseCase';
import { ReopenRaceUseCase } from '@core/racing/application/use-cases/ReopenRaceUseCase';
import { RequestProtestDefenseUseCase } from '@core/racing/application/use-cases/RequestProtestDefenseUseCase';
import { ReviewProtestUseCase } from '@core/racing/application/use-cases/ReviewProtestUseCase';
import { WithdrawFromRaceUseCase } from '@core/racing/application/use-cases/WithdrawFromRaceUseCase';

// Presenters
import { AllRacesPageDataPresenter } from './presenters/AllRacesPageDataPresenter';
import { CommandResultPresenter } from './presenters/CommandResultPresenter';
import { GetAllRacesPresenter } from './presenters/GetAllRacesPresenter';
import { GetTotalRacesPresenter } from './presenters/GetTotalRacesPresenter';
import { ImportRaceResultsApiPresenter } from './presenters/ImportRaceResultsApiPresenter';
import { RaceDetailPresenter } from './presenters/RaceDetailPresenter';
import { RacePenaltiesPresenter } from './presenters/RacePenaltiesPresenter';
import { RaceProtestsPresenter } from './presenters/RaceProtestsPresenter';
import { RaceResultsDetailPresenter } from './presenters/RaceResultsDetailPresenter';
import { RacesPageDataPresenter } from './presenters/RacesPageDataPresenter';
import { RaceWithSOFPresenter } from './presenters/RaceWithSOFPresenter';

// Command DTOs
import { ApplyPenaltyCommandDTO } from './dtos/ApplyPenaltyCommandDTO';
import { FileProtestCommandDTO } from './dtos/FileProtestCommandDTO';
import { PenaltyTypesReferenceDTO } from './dtos/PenaltyTypesReferenceDTO';
import { QuickPenaltyCommandDTO } from './dtos/QuickPenaltyCommandDTO';
import { RequestProtestDefenseCommandDTO } from './dtos/RequestProtestDefenseCommandDTO';
import { ReviewProtestCommandDTO } from './dtos/ReviewProtestCommandDTO';

// Tokens
import { ApplyPenaltyUseCase } from '@core/racing/application/use-cases/ApplyPenaltyUseCase';
import {
    ALL_RACES_PAGE_DATA_PRESENTER_TOKEN,
    COMMAND_RESULT_PRESENTER_TOKEN,
    GET_ALL_RACES_PRESENTER_TOKEN,
    GET_TOTAL_RACES_PRESENTER_TOKEN,
    IMPORT_RACE_RESULTS_API_PRESENTER_TOKEN,
    LOGGER_TOKEN,
    RACE_DETAIL_PRESENTER_TOKEN,
    RACE_PENALTIES_PRESENTER_TOKEN,
    RACE_PROTESTS_PRESENTER_TOKEN,
    RACE_RESULTS_DETAIL_PRESENTER_TOKEN,
    RACE_WITH_SOF_PRESENTER_TOKEN,
    RACES_PAGE_DATA_PRESENTER_TOKEN
} from './RaceTokens';

import { penaltyTypeRequiresValue } from '@core/racing/domain/entities/penalty/Penalty';
import {
    getPenaltyValueKind,
    PENALTY_TYPE_VALUES,
    type PenaltyTypeValue,
} from '@core/racing/domain/entities/penalty/PenaltyType';

@Injectable()
export class RaceService {
  constructor(
    @Inject(GetAllRacesUseCase) private readonly getAllRacesUseCase: GetAllRacesUseCase,
    @Inject(GetTotalRacesUseCase) private readonly getTotalRacesUseCase: GetTotalRacesUseCase,
    @Inject(ImportRaceResultsApiUseCase) private readonly importRaceResultsApiUseCase: ImportRaceResultsApiUseCase,
    @Inject(GetRaceDetailUseCase) private readonly getRaceDetailUseCase: GetRaceDetailUseCase,
    @Inject(GetRacesPageDataUseCase) private readonly getRacesPageDataUseCase: GetRacesPageDataUseCase,
    @Inject(GetAllRacesPageDataUseCase) private readonly getAllRacesPageDataUseCase: GetAllRacesPageDataUseCase,
    @Inject(GetRaceResultsDetailUseCase) private readonly getRaceResultsDetailUseCase: GetRaceResultsDetailUseCase,
    @Inject(GetRaceWithSOFUseCase) private readonly getRaceWithSOFUseCase: GetRaceWithSOFUseCase,
    @Inject(GetRaceProtestsUseCase) private readonly getRaceProtestsUseCase: GetRaceProtestsUseCase,
    @Inject(GetRacePenaltiesUseCase) private readonly getRacePenaltiesUseCase: GetRacePenaltiesUseCase,
    @Inject(RegisterForRaceUseCase) private readonly registerForRaceUseCase: RegisterForRaceUseCase,
    @Inject(WithdrawFromRaceUseCase) private readonly withdrawFromRaceUseCase: WithdrawFromRaceUseCase,
    @Inject(CancelRaceUseCase) private readonly cancelRaceUseCase: CancelRaceUseCase,
    @Inject(CompleteRaceUseCase) private readonly completeRaceUseCase: CompleteRaceUseCase,
    @Inject(FileProtestUseCase) private readonly fileProtestUseCase: FileProtestUseCase,
    @Inject(QuickPenaltyUseCase) private readonly quickPenaltyUseCase: QuickPenaltyUseCase,
    @Inject(ApplyPenaltyUseCase) private readonly applyPenaltyUseCase: ApplyPenaltyUseCase,
    @Inject(RequestProtestDefenseUseCase) private readonly requestProtestDefenseUseCase: RequestProtestDefenseUseCase,
    @Inject(ReviewProtestUseCase) private readonly reviewProtestUseCase: ReviewProtestUseCase,
    @Inject(ReopenRaceUseCase) private readonly reopenRaceUseCase: ReopenRaceUseCase,
    @Inject(LOGGER_TOKEN) private readonly logger: Logger,
    // Injected presenters
    @Inject(GET_ALL_RACES_PRESENTER_TOKEN) private readonly getAllRacesPresenter: GetAllRacesPresenter,
    @Inject(GET_TOTAL_RACES_PRESENTER_TOKEN) private readonly getTotalRacesPresenter: GetTotalRacesPresenter,
    @Inject(IMPORT_RACE_RESULTS_API_PRESENTER_TOKEN) private readonly importRaceResultsApiPresenter: ImportRaceResultsApiPresenter,
    @Inject(RACE_DETAIL_PRESENTER_TOKEN) private readonly raceDetailPresenter: RaceDetailPresenter,
    @Inject(RACES_PAGE_DATA_PRESENTER_TOKEN) private readonly racesPageDataPresenter: RacesPageDataPresenter,
    @Inject(ALL_RACES_PAGE_DATA_PRESENTER_TOKEN) private readonly allRacesPageDataPresenter: AllRacesPageDataPresenter,
    @Inject(RACE_RESULTS_DETAIL_PRESENTER_TOKEN) private readonly raceResultsDetailPresenter: RaceResultsDetailPresenter,
    @Inject(RACE_WITH_SOF_PRESENTER_TOKEN) private readonly raceWithSOFPresenter: RaceWithSOFPresenter,
    @Inject(RACE_PROTESTS_PRESENTER_TOKEN) private readonly raceProtestsPresenter: RaceProtestsPresenter,
    @Inject(RACE_PENALTIES_PRESENTER_TOKEN) private readonly racePenaltiesPresenter: RacePenaltiesPresenter,
    @Inject(COMMAND_RESULT_PRESENTER_TOKEN) private readonly commandResultPresenter: CommandResultPresenter,
  ) {}

  async getAllRaces(): Promise<GetAllRacesPresenter> {
    this.logger.debug('[RaceService] Fetching all races.');
    const result = await this.getAllRacesUseCase.execute({});
    
    if (result.isErr()) {
      const error = result.unwrapErr();
      throw new NotFoundException(error.details?.message ?? 'Failed to get all races');
    }
    
    const value = result.unwrap();
    this.getAllRacesPresenter.present(value);
    return this.getAllRacesPresenter;
  }

  async getTotalRaces(): Promise<GetTotalRacesPresenter> {
    this.logger.debug('[RaceService] Fetching total races count.');
    const result = await this.getTotalRacesUseCase.execute({});
    
    if (result.isErr()) {
      const error = result.unwrapErr();
      throw new NotFoundException(error.details?.message ?? 'Failed to get total races');
    }
    
    const value = result.unwrap();
    this.getTotalRacesPresenter.present(value);
    return this.getTotalRacesPresenter;
  }

  async importRaceResults(input: ImportRaceResultsDTO): Promise<ImportRaceResultsApiPresenter> {
    this.logger.debug('Importing race results:', input);
    const result = await this.importRaceResultsApiUseCase.execute({ raceId: input.raceId, resultsFileContent: input.resultsFileContent });
    
    if (result.isErr()) {
      const error = result.unwrapErr();
      throw new NotFoundException(error.details?.message ?? 'Failed to import race results');
    }
    
    const value = result.unwrap();
    this.importRaceResultsApiPresenter.present(value);
    return this.importRaceResultsApiPresenter;
  }

  async getRaceDetail(params: GetRaceDetailParamsDTO): Promise<RaceDetailPresenter> {
    this.logger.debug('[RaceService] Fetching race detail:', params);
    const result = await this.getRaceDetailUseCase.execute(params);
    
    if (result.isErr()) {
      const error = result.unwrapErr();
      throw new NotFoundException(error.details?.message ?? 'Failed to get race detail');
    }
    
    const value = result.unwrap();
    this.raceDetailPresenter.present(value, params);
    return this.raceDetailPresenter;
  }

  async getRacesPageData(leagueId: string): Promise<RacesPageDataPresenter> {
    this.logger.debug('[RaceService] Fetching races page data.');
    const result = await this.getRacesPageDataUseCase.execute({ leagueId });
    
    if (result.isErr()) {
      const error = result.unwrapErr();
      throw new NotFoundException(error.details?.message ?? 'Failed to get races page data');
    }
    
    const value = result.unwrap();
    this.racesPageDataPresenter.present(value);
    return this.racesPageDataPresenter;
  }

  async getAllRacesPageData(): Promise<AllRacesPageDataPresenter> {
    this.logger.debug('[RaceService] Fetching all races page data.');
    const result = await this.getAllRacesPageDataUseCase.execute({});
    
    if (result.isErr()) {
      const error = result.unwrapErr();
      throw new NotFoundException(error.details?.message ?? 'Failed to get all races page data');
    }
    
    const value = result.unwrap();
    this.allRacesPageDataPresenter.present(value);
    return this.allRacesPageDataPresenter;
  }

  async getRaceResultsDetail(raceId: string): Promise<RaceResultsDetailPresenter> {
    this.logger.debug('[RaceService] Fetching race results detail:', { raceId });
    const result = await this.getRaceResultsDetailUseCase.execute({ raceId });
    
    if (result.isErr()) {
      const error = result.unwrapErr();
      throw new NotFoundException(error.details?.message ?? 'Failed to get race results detail');
    }
    
    const value = result.unwrap();
    this.raceResultsDetailPresenter.present(value);
    return this.raceResultsDetailPresenter;
  }

  async getRaceWithSOF(raceId: string): Promise<RaceWithSOFPresenter> {
    this.logger.debug('[RaceService] Fetching race with SOF:', { raceId });
    const result = await this.getRaceWithSOFUseCase.execute({ raceId });
    
    if (result.isErr()) {
      const error = result.unwrapErr();
      throw new NotFoundException(error.details?.message ?? 'Failed to get race with SOF');
    }
    
    const value = result.unwrap();
    this.raceWithSOFPresenter.present(value);
    return this.raceWithSOFPresenter;
  }

  async getRaceProtests(raceId: string): Promise<RaceProtestsPresenter> {
    this.logger.debug('[RaceService] Fetching race protests:', { raceId });
    const result = await this.getRaceProtestsUseCase.execute({ raceId });
    
    if (result.isErr()) {
      const error = result.unwrapErr();
      throw new NotFoundException(error.details?.message ?? 'Failed to get race protests');
    }
    
    const value = result.unwrap();
    this.raceProtestsPresenter.present(value);
    return this.raceProtestsPresenter;
  }

  getPenaltyTypesReference(): PenaltyTypesReferenceDTO {
    const items = PENALTY_TYPE_VALUES.map((type: PenaltyTypeValue) => ({
      type,
      requiresValue: penaltyTypeRequiresValue(type),
      valueKind: getPenaltyValueKind(type),
    }));

    return {
      penaltyTypes: items,
      defaultReasons: {
        upheld: 'Protest upheld',
        dismissed: 'Protest dismissed',
      },
    };
  }

  async getRacePenalties(raceId: string): Promise<RacePenaltiesPresenter> {
    this.logger.debug('[RaceService] Fetching race penalties:', { raceId });
    const result = await this.getRacePenaltiesUseCase.execute({ raceId });
    
    if (result.isErr()) {
      const error = result.unwrapErr();
      throw new NotFoundException(error.details?.message ?? 'Failed to get race penalties');
    }
    
    const value = result.unwrap();
    this.racePenaltiesPresenter.present(value);
    return this.racePenaltiesPresenter;
  }

  async registerForRace(params: RegisterForRaceParamsDTO): Promise<CommandResultPresenter> {
    this.logger.debug('[RaceService] Registering for race:', params);
    const result = await this.registerForRaceUseCase.execute(params);
    
    if (result.isErr()) {
      const error = result.unwrapErr();
      throw new NotFoundException(error.details?.message ?? 'Failed to register for race');
    }
    
    this.commandResultPresenter.present();
    return this.commandResultPresenter;
  }

  async withdrawFromRace(params: WithdrawFromRaceParamsDTO): Promise<CommandResultPresenter> {
    this.logger.debug('[RaceService] Withdrawing from race:', params);
    const result = await this.withdrawFromRaceUseCase.execute(params);
    
    if (result.isErr()) {
      const error = result.unwrapErr();
      throw new NotFoundException(error.details?.message ?? 'Failed to withdraw from race');
    }
    
    this.commandResultPresenter.present();
    return this.commandResultPresenter;
  }

  async cancelRace(params: RaceActionParamsDTO, cancelledById: string): Promise<CommandResultPresenter> {
    this.logger.debug('[RaceService] Cancelling race:', params);
    const result = await this.cancelRaceUseCase.execute({ raceId: params.raceId, cancelledById });
    
    if (result.isErr()) {
      const error = result.unwrapErr();
      throw new NotFoundException(error.details?.message ?? 'Failed to cancel race');
    }
    
    this.commandResultPresenter.present();
    return this.commandResultPresenter;
  }

  async completeRace(params: RaceActionParamsDTO): Promise<CommandResultPresenter> {
    this.logger.debug('[RaceService] Completing race:', params);
    const result = await this.completeRaceUseCase.execute({ raceId: params.raceId });
    
    if (result.isErr()) {
      const error = result.unwrapErr();
      throw new NotFoundException(error.details?.message ?? 'Failed to complete race');
    }
    
    this.commandResultPresenter.present();
    return this.commandResultPresenter;
  }

  async reopenRace(params: RaceActionParamsDTO, reopenedById: string): Promise<CommandResultPresenter> {
    this.logger.debug('[RaceService] Re-opening race:', params);
    const result = await this.reopenRaceUseCase.execute({ raceId: params.raceId, reopenedById });
    
    if (result.isErr()) {
      const error = result.unwrapErr();
      throw new NotFoundException(error.details?.message ?? 'Failed to reopen race');
    }
    
    this.commandResultPresenter.present();
    return this.commandResultPresenter;
  }

  async fileProtest(command: FileProtestCommandDTO): Promise<CommandResultPresenter> {
    this.logger.debug('[RaceService] Filing protest:', command);
    const result = await this.fileProtestUseCase.execute(command);
    
    if (result.isErr()) {
      const error = result.unwrapErr();
      throw new NotFoundException(error.details?.message ?? 'Failed to file protest');
    }
    
    this.commandResultPresenter.present();
    return this.commandResultPresenter;
  }

  async applyQuickPenalty(command: QuickPenaltyCommandDTO): Promise<CommandResultPresenter> {
    this.logger.debug('[RaceService] Applying quick penalty:', command);
    const result = await this.quickPenaltyUseCase.execute(command);
    
    if (result.isErr()) {
      const error = result.unwrapErr();
      throw new NotFoundException(error.details?.message ?? 'Failed to apply quick penalty');
    }
    
    this.commandResultPresenter.present();
    return this.commandResultPresenter;
  }

  async applyPenalty(command: ApplyPenaltyCommandDTO): Promise<CommandResultPresenter> {
    this.logger.debug('[RaceService] Applying penalty:', command);
    const result = await this.applyPenaltyUseCase.execute(command);
    
    if (result.isErr()) {
      // ApplyPenaltyUseCase errors don't have details, just code
      throw new NotFoundException('Failed to apply penalty');
    }
    
    this.commandResultPresenter.present();
    return this.commandResultPresenter;
  }

  async requestProtestDefense(command: RequestProtestDefenseCommandDTO): Promise<CommandResultPresenter> {
    this.logger.debug('[RaceService] Requesting protest defense:', command);
    const result = await this.requestProtestDefenseUseCase.execute(command);
    
    if (result.isErr()) {
      const error = result.unwrapErr();
      throw new NotFoundException(error.details?.message ?? 'Failed to request protest defense');
    }
    
    this.commandResultPresenter.present();
    return this.commandResultPresenter;
  }

  async reviewProtest(command: ReviewProtestCommandDTO): Promise<CommandResultPresenter> {
    this.logger.debug('[RaceService] Reviewing protest:', command);
    const result = await this.reviewProtestUseCase.execute(command);
    
    if (result.isErr()) {
      const error = result.unwrapErr();
      throw new NotFoundException(error.details?.message ?? 'Failed to review protest');
    }
    
    this.commandResultPresenter.present();
    return this.commandResultPresenter;
  }
}