import { Body, Controller, Get, HttpCode, HttpStatus, Inject, InternalServerErrorException, Param, Post, Query } from '@nestjs/common';
import { ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
import { Public } from '../auth/Public';
import { RaceService } from './RaceService';
import { AllRacesPageDTO } from './dtos/AllRacesPageDTO';
import { ApplyPenaltyCommandDTO } from './dtos/ApplyPenaltyCommandDTO';
import { FileProtestCommandDTO } from './dtos/FileProtestCommandDTO';
import { ImportRaceResultsDTO } from './dtos/ImportRaceResultsDTO';
import { ImportRaceResultsSummaryDTO } from './dtos/ImportRaceResultsSummaryDTO';
import { PenaltyTypesReferenceDTO } from './dtos/PenaltyTypesReferenceDTO';
import { QuickPenaltyCommandDTO } from './dtos/QuickPenaltyCommandDTO';
import { RaceDetailDTO } from './dtos/RaceDetailDTO';
import { RacePenaltiesDTO } from './dtos/RacePenaltiesDTO';
import { RaceProtestsDTO } from './dtos/RaceProtestsDTO';
import { RaceResultsDetailDTO } from './dtos/RaceResultsDetailDTO';
import { RaceStatsDTO } from './dtos/RaceStatsDTO';
import { RaceWithSOFDTO } from './dtos/RaceWithSOFDTO';
import { RacesPageDataDTO } from './dtos/RacesPageDataDTO';
import { RegisterForRaceParamsDTO } from './dtos/RegisterForRaceParamsDTO';
import { RequestProtestDefenseCommandDTO } from './dtos/RequestProtestDefenseCommandDTO';
import { WithdrawFromRaceParamsDTO } from './dtos/WithdrawFromRaceParamsDTO';

@ApiTags('races')
@Controller('races')
export class RaceController {
  constructor(@Inject(RaceService) private readonly raceService: RaceService) {}

  @Public()
  @Get('all')
  @ApiOperation({ summary: 'Get all races' })
  @ApiResponse({ status: 200, description: 'List of all races', type: AllRacesPageDTO })
  async getAllRaces(): Promise<AllRacesPageDTO> {
    const presenter = await this.raceService.getAllRaces();
    return presenter.viewModel;
  }

  @Public()
  @Get('total-races')
  @ApiOperation({ summary: 'Get the total number of races' })
  @ApiResponse({ status: 200, description: 'Total number of races', type: RaceStatsDTO })
  async getTotalRaces(): Promise<RaceStatsDTO> {
    const presenter = await this.raceService.getTotalRaces();
    return presenter.viewModel;
  }

  @Public()
  @Get('page-data')
  @ApiOperation({ summary: 'Get races page data' })
  @ApiQuery({ name: 'leagueId', description: 'League ID' })
  @ApiResponse({ status: 200, description: 'Races page data', type: RacesPageDataDTO })
  async getRacesPageData(@Query('leagueId') leagueId: string): Promise<RacesPageDataDTO> {
    const presenter = await this.raceService.getRacesPageData(leagueId);
    return presenter.viewModel;
  }

  @Public()
  @Get('all/page-data')
  @ApiOperation({ summary: 'Get all races page data' })
  @ApiResponse({ status: 200, description: 'All races page data', type: AllRacesPageDTO })
  async getAllRacesPageData(): Promise<AllRacesPageDTO> {
    const presenter = await this.raceService.getAllRacesPageData();
    return presenter.viewModel;
  }

  @Public()
  @Get(':raceId')
  @ApiOperation({ summary: 'Get race detail' })
  @ApiParam({ name: 'raceId', description: 'Race ID' })
  @ApiQuery({ name: 'driverId', description: 'Driver ID' })
  @ApiResponse({ status: 200, description: 'Race detail', type: RaceDetailDTO })
  async getRaceDetail(
    @Param('raceId') raceId: string,
    @Query('driverId') driverId?: string,
  ): Promise<RaceDetailDTO> {
    const params = driverId ? { raceId, driverId } : { raceId };
    const presenter = await this.raceService.getRaceDetail(params);
    return await presenter.viewModel;
  }

  @Public()
  @Get(':raceId/results')
  @ApiOperation({ summary: 'Get race results detail' })
  @ApiParam({ name: 'raceId', description: 'Race ID' })
  @ApiResponse({ status: 200, description: 'Race results detail', type: RaceResultsDetailDTO })
  async getRaceResultsDetail(@Param('raceId') raceId: string): Promise<RaceResultsDetailDTO> {
    const presenter = await this.raceService.getRaceResultsDetail(raceId);
    return await presenter.viewModel;
  }

  @Public()
  @Get(':raceId/sof')
  @ApiOperation({ summary: 'Get race with strength of field' })
  @ApiParam({ name: 'raceId', description: 'Race ID' })
  @ApiResponse({ status: 200, description: 'Race with SOF', type: RaceWithSOFDTO })
  async getRaceWithSOF(@Param('raceId') raceId: string): Promise<RaceWithSOFDTO> {
    const presenter = await this.raceService.getRaceWithSOF(raceId);
    return presenter.viewModel;
  }

  @Public()
  @Get(':raceId/protests')
  @ApiOperation({ summary: 'Get race protests' })
  @ApiParam({ name: 'raceId', description: 'Race ID' })
  @ApiResponse({ status: 200, description: 'Race protests', type: RaceProtestsDTO })
  async getRaceProtests(@Param('raceId') raceId: string): Promise<RaceProtestsDTO> {
    const presenter = await this.raceService.getRaceProtests(raceId);
    return presenter.viewModel;
  }

  @Public()
  @Get('reference/penalty-types')
  @ApiOperation({ summary: 'Get allowed penalty types and semantics' })
  @ApiResponse({ status: 200, description: 'Penalty types reference', type: PenaltyTypesReferenceDTO })
  getPenaltyTypesReference(): PenaltyTypesReferenceDTO {
    return this.raceService.getPenaltyTypesReference();
  }

  @Public()
  @Get(':raceId/penalties')
  @ApiOperation({ summary: 'Get race penalties' })
  @ApiParam({ name: 'raceId', description: 'Race ID' })
  @ApiResponse({ status: 200, description: 'Race penalties', type: RacePenaltiesDTO })
  async getRacePenalties(@Param('raceId') raceId: string): Promise<RacePenaltiesDTO> {
    const presenter = await this.raceService.getRacePenalties(raceId);
    return presenter.viewModel;
  }

  @Post(':raceId/register')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Register for race' })
  @ApiParam({ name: 'raceId', description: 'Race ID' })
  @ApiResponse({ status: 200, description: 'Successfully registered for race' })
  async registerForRace(
    @Param('raceId') raceId: string,
    @Body() body: Omit<RegisterForRaceParamsDTO, 'raceId'>,
  ): Promise<void> {
    const presenter = await this.raceService.registerForRace({ raceId, ...body });
    const viewModel = presenter.viewModel;

    if (!viewModel.success) {
      throw new InternalServerErrorException(viewModel.message ?? 'Failed to register for race');
    }
  }

  @Post(':raceId/withdraw')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Withdraw from race' })
  @ApiParam({ name: 'raceId', description: 'Race ID' })
  @ApiResponse({ status: 200, description: 'Successfully withdrew from race' })
  async withdrawFromRace(
    @Param('raceId') raceId: string,
    @Body() body: Omit<WithdrawFromRaceParamsDTO, 'raceId'>,
  ): Promise<void> {
    const presenter = await this.raceService.withdrawFromRace({ raceId, ...body });
    const viewModel = presenter.viewModel;

    if (!viewModel.success) {
      throw new InternalServerErrorException(viewModel.message ?? 'Failed to withdraw from race');
    }
  }

  @Post(':raceId/cancel')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Cancel race' })
  @ApiParam({ name: 'raceId', description: 'Race ID' })
  @ApiResponse({ status: 200, description: 'Successfully cancelled race' })
  async cancelRace(@Param('raceId') raceId: string): Promise<void> {
    const presenter = await this.raceService.cancelRace({ raceId }, '');
    const viewModel = presenter.viewModel;

    if (!viewModel.success) {
      throw new InternalServerErrorException(viewModel.message ?? 'Failed to cancel race');
    }
  }

  @Post(':raceId/complete')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Complete race' })
  @ApiParam({ name: 'raceId', description: 'Race ID' })
  @ApiResponse({ status: 200, description: 'Successfully completed race' })
  async completeRace(@Param('raceId') raceId: string): Promise<void> {
    const presenter = await this.raceService.completeRace({ raceId });
    const viewModel = presenter.viewModel;

    if (!viewModel.success) {
      throw new InternalServerErrorException(viewModel.message ?? 'Failed to complete race');
    }
  }

  @Post(':raceId/reopen')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Re-open race' })
  @ApiParam({ name: 'raceId', description: 'Race ID' })
  @ApiResponse({ status: 200, description: 'Successfully re-opened race' })
  async reopenRace(@Param('raceId') raceId: string): Promise<void> {
    const presenter = await this.raceService.reopenRace({ raceId }, '');
    const viewModel = presenter.viewModel;

    if (!viewModel.success) {
      throw new InternalServerErrorException(viewModel.message ?? 'Failed to re-open race');
    }
  }

  @Post(':raceId/import-results')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Import race results' })
  @ApiParam({ name: 'raceId', description: 'Race ID' })
  @ApiResponse({ status: 200, description: 'Successfully imported race results', type: ImportRaceResultsSummaryDTO })
  async importRaceResults(
    @Param('raceId') raceId: string,
    @Body() body: Omit<ImportRaceResultsDTO, 'raceId'>,
  ): Promise<ImportRaceResultsSummaryDTO> {
    const presenter = await this.raceService.importRaceResults({ raceId, ...body });
    return presenter.viewModel;
  }

  @Post('protests/file')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'File a protest' })
  @ApiResponse({ status: 200, description: 'Protest filed successfully' })
  async fileProtest(@Body() body: FileProtestCommandDTO): Promise<void> {
    const presenter = await this.raceService.fileProtest(body);
    const viewModel = presenter.viewModel;

    if (!viewModel.success) {
      throw new InternalServerErrorException(viewModel.message ?? 'Failed to file protest');
    }
  }

  @Post('penalties/quick')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Apply a quick penalty' })
  @ApiResponse({ status: 200, description: 'Penalty applied successfully' })
  async applyQuickPenalty(@Body() body: QuickPenaltyCommandDTO): Promise<void> {
    const presenter = await this.raceService.applyQuickPenalty(body);
    const viewModel = presenter.viewModel;

    if (!viewModel.success) {
      throw new InternalServerErrorException(viewModel.message ?? 'Failed to apply quick penalty');
    }
  }

  @Post('penalties/apply')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Apply a penalty' })
  @ApiResponse({ status: 200, description: 'Penalty applied successfully' })
  async applyPenalty(@Body() body: ApplyPenaltyCommandDTO): Promise<void> {
    const presenter = await this.raceService.applyPenalty(body);
    const viewModel = presenter.viewModel;

    if (!viewModel.success) {
      throw new InternalServerErrorException(viewModel.message ?? 'Failed to apply penalty');
    }
  }

  @Post('protests/defense/request')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Request protest defense' })
  @ApiResponse({ status: 200, description: 'Defense requested successfully' })
  async requestProtestDefense(@Body() body: RequestProtestDefenseCommandDTO): Promise<void> {
    const presenter = await this.raceService.requestProtestDefense(body);
    const viewModel = presenter.viewModel;

    if (!viewModel.success) {
      throw new InternalServerErrorException(viewModel.message ?? 'Failed to request protest defense');
    }
  }
}
