import type { IRaceRepository } from '../../domain/repositories/IRaceRepository';
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
import type { IDriverRepository } from '../../domain/repositories/IDriverRepository';
import type { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository';
import type { IResultRepository } from '../../domain/repositories/IResultRepository';
import type { ILeagueMembershipRepository } from '../../domain/repositories/ILeagueMembershipRepository';
import type { DriverRatingProvider } from '../ports/DriverRatingProvider';
import type { IImageServicePort } from '../ports/IImageServicePort';
import type {
  RaceDetailViewModel,
  RaceDetailRaceViewModel,
  RaceDetailLeagueViewModel,
  RaceDetailEntryViewModel,
  RaceDetailUserResultViewModel,
} from '../presenters/IRaceDetailPresenter';
import type { AsyncUseCase } from '@core/shared/application/AsyncUseCase';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';

/**
 * Use Case: GetRaceDetailUseCase
 *
 * Given a race id and current driver id:
 * - When the race exists, it builds a view model with race, league, entry list, registration flags and user result.
 * - When the race does not exist, it presents a view model with an error and no race data.
 *
 * Given a completed race with a result for the driver:
 * - When computing rating change, it applies the same position-based formula used in the legacy UI.
 */
export interface GetRaceDetailQueryParams {
  raceId: string;
  driverId: string;
}

type GetRaceDetailErrorCode = 'RACE_NOT_FOUND';

export class GetRaceDetailUseCase
  implements AsyncUseCase<GetRaceDetailQueryParams, RaceDetailViewModel, GetRaceDetailErrorCode>
{
  constructor(
    private readonly raceRepository: IRaceRepository,
    private readonly leagueRepository: ILeagueRepository,
    private readonly driverRepository: IDriverRepository,
    private readonly raceRegistrationRepository: IRaceRegistrationRepository,
    private readonly resultRepository: IResultRepository,
    private readonly leagueMembershipRepository: ILeagueMembershipRepository,
    private readonly driverRatingProvider: DriverRatingProvider,
    private readonly imageService: IImageServicePort,
  ) {}

  async execute(params: GetRaceDetailQueryParams): Promise<Result<RaceDetailViewModel, ApplicationErrorCode<GetRaceDetailErrorCode>>> {
    const { raceId, driverId } = params;

    const race = await this.raceRepository.findById(raceId);
    if (!race) {
      return Result.err({ code: 'RACE_NOT_FOUND' });
    }

    const [league, registeredDriverIds, membership] = await Promise.all([
      this.leagueRepository.findById(race.leagueId),
      this.raceRegistrationRepository.getRegisteredDrivers(race.id),
      this.leagueMembershipRepository.getMembership(race.leagueId, driverId),
    ]);

    const ratings = this.driverRatingProvider.getRatings(registeredDriverIds);

    const drivers = await Promise.all(
      registeredDriverIds.map(id => this.driverRepository.findById(id)),
    );

    const entryList: RaceDetailEntryViewModel[] = drivers
      .filter((d): d is NonNullable<typeof d> => d !== null)
      .map(driver => ({
        id: driver.id,
        name: driver.name,
        country: driver.country,
        avatarUrl: this.imageService.getDriverAvatar(driver.id),
        rating: ratings.get(driver.id) ?? null,
        isCurrentUser: driver.id === driverId,
      }));

    const isUserRegistered = registeredDriverIds.includes(driverId);
    const isUpcoming = race.status === 'scheduled' && race.scheduledAt > new Date();
    const canRegister = !!membership && membership.status === 'active' && isUpcoming;

    let userResultView: RaceDetailUserResultViewModel | null = null;

    if (race.status === 'completed') {
      const results = await this.resultRepository.findByRaceId(race.id);
      const userResult = results.find(r => r.driverId === driverId) ?? null;

      if (userResult) {
        const ratingChange = this.calculateRatingChange(userResult.position);

        userResultView = {
          position: userResult.position,
          startPosition: userResult.startPosition,
          incidents: userResult.incidents,
          fastestLap: userResult.fastestLap,
          positionChange: userResult.getPositionChange(),
          isPodium: userResult.isPodium(),
          isClean: userResult.isClean(),
          ratingChange,
        };
      }
    }

    const raceView: RaceDetailRaceViewModel = {
      id: race.id,
      leagueId: race.leagueId,
      track: race.track,
      car: race.car,
      scheduledAt: race.scheduledAt.toISOString(),
      sessionType: race.sessionType,
      status: race.status,
      strengthOfField: race.strengthOfField ?? null,
      ...(race.registeredCount !== undefined ? { registeredCount: race.registeredCount } : {}),
      ...(race.maxParticipants !== undefined ? { maxParticipants: race.maxParticipants } : {}),
    };

    const leagueView: RaceDetailLeagueViewModel | null = league
      ? {
          id: league.id,
          name: league.name,
          description: league.description,
          settings: {
            ...(league.settings.maxDrivers !== undefined
              ? { maxDrivers: league.settings.maxDrivers }
              : {}),
            ...(league.settings.qualifyingFormat !== undefined
              ? { qualifyingFormat: league.settings.qualifyingFormat }
              : {}),
          },
        }
      : null;

    const viewModel: RaceDetailViewModel = {
      race: raceView,
      league: leagueView,
      entryList,
      registration: {
        isUserRegistered,
        canRegister,
      },
      userResult: userResultView,
    };

    return Result.ok(viewModel);
  }

  private calculateRatingChange(position: number): number {
    const baseChange = position <= 3 ? 25 : position <= 10 ? 10 : -5;
    const positionBonus = Math.max(0, (20 - position) * 2);
    return baseChange + positionBonus;
  }
}