import { SeasonScheduleGenerator } from '../../domain/services/SeasonScheduleGenerator';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { Logger } from '@core/shared/application';
import { SeasonSchedule } from '../../domain/value-objects/SeasonSchedule';
import { RaceTimeOfDay } from '../../domain/value-objects/RaceTimeOfDay';
import { LeagueTimezone } from '../../domain/value-objects/LeagueTimezone';
import { RecurrenceStrategyFactory } from '../../domain/value-objects/RecurrenceStrategy';
import { WeekdaySet } from '../../domain/value-objects/WeekdaySet';
import { MonthlyRecurrencePattern } from '../../domain/value-objects/MonthlyRecurrencePattern';
import { ALL_WEEKDAYS, type Weekday } from '../../domain/types/Weekday';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';

export type SeasonScheduleConfig = {
  seasonStartDate: string;
  recurrenceStrategy: 'weekly' | 'everyNWeeks' | 'monthlyNthWeekday';
  weekdays?: string[];
  raceStartTime: string;
  timezoneId: string;
  plannedRounds: number;
  intervalWeeks?: number;
  monthlyOrdinal?: 1 | 2 | 3 | 4;
  monthlyWeekday?: string;
};

function toWeekdaySet(values: string[] | undefined): WeekdaySet {
  const weekdays = (values ?? []).filter((v): v is Weekday =>
    ALL_WEEKDAYS.includes(v as Weekday),
  );

  return WeekdaySet.fromArray(weekdays.length > 0 ? weekdays : ['Mon']);
}

function scheduleConfigToSeasonSchedule(dto: SeasonScheduleConfig): SeasonSchedule {
  const startDate = new Date(dto.seasonStartDate);
  const timeOfDay = RaceTimeOfDay.fromString(dto.raceStartTime);
  const timezone = LeagueTimezone.create(dto.timezoneId);

  const recurrence = (() => {
    switch (dto.recurrenceStrategy) {
      case 'everyNWeeks':
        return RecurrenceStrategyFactory.everyNWeeks(
          dto.intervalWeeks ?? 2,
          toWeekdaySet(dto.weekdays),
        );
      case 'monthlyNthWeekday': {
        const pattern = MonthlyRecurrencePattern.create(
          dto.monthlyOrdinal ?? 1,
          ((dto.monthlyWeekday ?? 'Mon') as Weekday),
        );
        return RecurrenceStrategyFactory.monthlyNthWeekday(pattern);
      }
      case 'weekly':
      default:
        return RecurrenceStrategyFactory.weekly(toWeekdaySet(dto.weekdays));
    }
  })();

  return new SeasonSchedule({
    startDate,
    timeOfDay,
    timezone,
    recurrence,
    plannedRounds: dto.plannedRounds,
  });
}

export type PreviewLeagueScheduleSeasonConfig = SeasonScheduleConfig;

export type PreviewLeagueScheduleInput = {
  schedule: PreviewLeagueScheduleSeasonConfig;
  maxRounds?: number;
};

export interface PreviewLeagueScheduleRound {
  roundNumber: number;
  scheduledAt: string;
  timezoneId: string;
}

export interface PreviewLeagueScheduleResult {
  rounds: PreviewLeagueScheduleRound[];
  summary: string;
}

export type PreviewLeagueScheduleErrorCode =
  | 'INVALID_SCHEDULE'
  | 'REPOSITORY_ERROR';

export class PreviewLeagueScheduleUseCase {
  constructor(
    private readonly scheduleGenerator: Pick<
      typeof SeasonScheduleGenerator,
      'generateSlotsUpTo'
    > = SeasonScheduleGenerator,
    private readonly logger: Logger,
    private readonly output: UseCaseOutputPort<PreviewLeagueScheduleResult>,
  ) {}

  async execute(
    params: PreviewLeagueScheduleInput,
  ): Promise<
    Result<
      void,
      ApplicationErrorCode<PreviewLeagueScheduleErrorCode, { message: string }>
    >
  > {
    this.logger.debug('Previewing league schedule', { params });

    try {
      let seasonSchedule: SeasonSchedule;
      try {
        seasonSchedule = scheduleConfigToSeasonSchedule(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: PreviewLeagueScheduleRound[] = slots.map(slot => ({
        roundNumber: slot.roundNumber,
        scheduledAt: slot.scheduledAt.toISOString(),
        timezoneId: slot.timezone.id,
      }));

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

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

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

      this.output.present(result);

      return Result.ok(undefined);
    } catch (error) {
      this.logger.error(
        'Failed to preview 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 preview league schedule',
        },
      });
    }
  }

  private buildSummary(
    schedule: PreviewLeagueScheduleSeasonConfig,
    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`;
    }
  }
}