import type { Logger, UseCaseOutputPort } from '@core/shared/application';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { League } from '../../domain/entities/League';
import type { Race } from '../../domain/entities/Race';
import type { Season } from '../../domain/entities/season/Season';
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
import type { IRaceRepository } from '../../domain/repositories/IRaceRepository';
import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository';
import { SeasonScheduleGenerator } from '../../domain/services/SeasonScheduleGenerator';

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

export interface GetLeagueScheduleInput {
  leagueId: string;
  seasonId?: string;
}

export interface LeagueScheduledRace {
  race: Race;
}

export interface GetLeagueScheduleResult {
  league: League;
  seasonId: string;
  published: boolean;
  races: LeagueScheduledRace[];
}

export class GetLeagueScheduleUseCase {
  constructor(
    private readonly leagueRepository: ILeagueRepository,
    private readonly seasonRepository: ISeasonRepository,
    private readonly raceRepository: IRaceRepository,
    private readonly logger: Logger,
    private readonly output: UseCaseOutputPort<GetLeagueScheduleResult>,
  ) {}

  private async resolveSeasonForSchedule(params: {
    leagueId: string;
    requestedSeasonId?: string;
  }): Promise<Result<Season, ApplicationErrorCode<GetLeagueScheduleErrorCode, { message: string }>>> {
    if (params.requestedSeasonId) {
      const season = await this.seasonRepository.findById(params.requestedSeasonId);
      if (!season || season.leagueId !== params.leagueId) {
        return Result.err({
          code: 'SEASON_NOT_FOUND',
          details: { message: 'Season not found for league' },
        });
      }
      return Result.ok(season);
    }

    const seasons = await this.seasonRepository.findByLeagueId(params.leagueId);
    const activeSeason = seasons.find(s => s.status.isActive()) ?? seasons[0];
    if (!activeSeason) {
      return Result.err({
        code: 'SEASON_NOT_FOUND',
        details: { message: 'No seasons found for league' },
      });
    }

    return Result.ok(activeSeason);
  }

  private getSeasonDateWindow(season: Season): { start?: Date; endInclusive?: Date } {
    const start = season.startDate ?? season.schedule?.startDate;
    const window: { start?: Date; endInclusive?: Date } = {};

    if (start) {
      window.start = start;
    }

    if (season.endDate) {
      window.endInclusive = season.endDate;
      return window;
    }

    if (season.schedule) {
      const slots = SeasonScheduleGenerator.generateSlotsUpTo(
        season.schedule,
        season.schedule.plannedRounds,
      );
      const last = slots.at(-1);
      if (last?.scheduledAt) {
        window.endInclusive = last.scheduledAt;
      }
      return window;
    }

    return window;
  }

  private filterRacesBySeasonWindow(season: Season, races: Race[]): Race[] {
    const { start, endInclusive } = this.getSeasonDateWindow(season);

    if (!start && !endInclusive) return races;

    return races.filter(race => {
      const t = race.scheduledAt.getTime();
      if (start && t < start.getTime()) return false;
      if (endInclusive && t > endInclusive.getTime()) return false;
      return true;
    });
  }

  async execute(
    input: GetLeagueScheduleInput,
  ): Promise<
    Result<void, ApplicationErrorCode<GetLeagueScheduleErrorCode, { message: string }>>
  > {
    this.logger.debug('Fetching league schedule', { input });
    const { leagueId } = input;

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

      const seasonResult = await this.resolveSeasonForSchedule({
        leagueId,
        ...(input.seasonId ? { requestedSeasonId: input.seasonId } : {}),
      });
      if (seasonResult.isErr()) {
        return Result.err(seasonResult.unwrapErr());
      }
      const season = seasonResult.unwrap();

      const races = await this.raceRepository.findByLeagueId(leagueId);
      const seasonRaces = this.filterRacesBySeasonWindow(season, races);

      const scheduledRaces: LeagueScheduledRace[] = seasonRaces.map(race => ({
        race,
      }));

      const result: GetLeagueScheduleResult = {
        league,
        seasonId: season.id,
        published: season.schedulePublished ?? false,
        races: scheduledRaces,
      };

      this.output.present(result);

      return Result.ok(undefined);
    } catch (error) {
      this.logger.error(
        'Failed to load league schedule due to an unexpected error',
        error instanceof Error ? error : new Error('Unknown error'),
      );

      return Result.err({
        code: 'REPOSITORY_ERROR',
        details: {
          message:
            error instanceof Error
              ? error.message
              : 'Failed to load league schedule',
        },
      });
    }
  }
}