import { SeasonScheduleGenerator } from '../../domain/services/SeasonScheduleGenerator';
import type { LeagueSchedulePreviewDTO, LeagueScheduleDTO } from '../dto/LeagueScheduleDTO';
import { scheduleDTOToSeasonSchedule } from '../dto/LeagueScheduleDTO';
import type { AsyncUseCase } from '@core/shared/application/AsyncUseCase';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Logger } from '@core/shared/application';
import type { SeasonSchedule } from '../../domain/value-objects/SeasonSchedule';

interface PreviewLeagueScheduleQueryParams {
  schedule: LeagueScheduleDTO;
  maxRounds?: number;
}

type PreviewLeagueScheduleErrorCode = 'INVALID_SCHEDULE';

type PreviewLeagueScheduleApplicationError = ApplicationErrorCode<PreviewLeagueScheduleErrorCode, { message: string }>;

export class PreviewLeagueScheduleUseCase implements AsyncUseCase<PreviewLeagueScheduleQueryParams, LeagueSchedulePreviewDTO, PreviewLeagueScheduleErrorCode> {
  constructor(
    private readonly scheduleGenerator: typeof SeasonScheduleGenerator = SeasonScheduleGenerator,
    private readonly logger: Logger,
  ) {}

  async execute(params: PreviewLeagueScheduleQueryParams): Promise<Result<LeagueSchedulePreviewDTO, PreviewLeagueScheduleApplicationError>> {
    this.logger.debug('Previewing league schedule', { params });

    let seasonSchedule: SeasonSchedule;
    try {
      seasonSchedule = scheduleDTOToSeasonSchedule(params.schedule);
    } catch (error) {
      this.logger.warn('Invalid schedule data provided', { schedule: params.schedule, error: error instanceof Error ? error.message : 'Unknown error' });
      return Result.err({ code: 'INVALID_SCHEDULE', details: { message: 'Invalid schedule data' } });
    }

    const maxRounds =
      params.maxRounds && params.maxRounds > 0
        ? Math.min(params.maxRounds, seasonSchedule.plannedRounds)
        : seasonSchedule.plannedRounds;

    const slots = this.scheduleGenerator.generateSlotsUpTo(seasonSchedule, maxRounds);

    const rounds = slots.map((slot) => ({
      roundNumber: slot.roundNumber,
      scheduledAt: slot.scheduledAt.toISOString(),
      timezoneId: slot.timezone.getId(),
    }));

    const summary = this.buildSummary(params.schedule, rounds);

    const result: LeagueSchedulePreviewDTO = {
      rounds,
      summary,
    };

    this.logger.info('Successfully generated league schedule preview', { roundCount: rounds.length });
    return Result.ok(result);
  }

  private buildSummary(
    schedule: LeagueScheduleDTO,
    rounds: Array<{ roundNumber: number; scheduledAt: string; timezoneId: string }>,
  ): string {
    if (rounds.length === 0) {
      return 'No rounds scheduled.';
    }

    const firstRound = rounds[0]!;
    const lastRound = rounds[rounds.length - 1]!;

    const first = new Date(firstRound.scheduledAt);
    const last = new Date(lastRound.scheduledAt);

    const firstDate = first.toISOString().slice(0, 10);
    const lastDate = last.toISOString().slice(0, 10);

    const timePart = schedule.raceStartTime;
    const tz = schedule.timezoneId;

    let recurrenceDescription: string;

    if (schedule.recurrenceStrategy === 'weekly') {
      const days = (schedule.weekdays ?? []).join(', ');
      recurrenceDescription = `Every ${days}`;
    } else if (schedule.recurrenceStrategy === 'everyNWeeks') {
      const interval = schedule.intervalWeeks ?? 1;
      const days = (schedule.weekdays ?? []).join(', ');
      recurrenceDescription = `Every ${interval} week(s) on ${days}`;
    } else if (schedule.recurrenceStrategy === 'monthlyNthWeekday') {
      const ordinalLabel = this.ordinalToLabel(schedule.monthlyOrdinal ?? 1);
      const weekday = schedule.monthlyWeekday ?? 'Mon';
      recurrenceDescription = `Every ${ordinalLabel} ${weekday}`;
    } else {
      recurrenceDescription = 'Custom recurrence';
    }

    return `${recurrenceDescription} at ${timePart} ${tz}, starting ${firstDate} — ${rounds.length} rounds from ${firstDate} to ${lastDate}.`;
  }

  private ordinalToLabel(ordinal: 1 | 2 | 3 | 4): string {
    switch (ordinal) {
      case 1:
        return '1st';
      case 2:
        return '2nd';
      case 3:
        return '3rd';
      case 4:
        return '4th';
      default:
        return `${ordinal}th`;
    }
  }
}