import { Season } from '../../domain/entities/Season';
import type { ISeasonRepository } from '../../domain/repositories/ISeasonRepository';
import type { ILeagueRepository } from '../../domain/repositories/ILeagueRepository';
import type { LeagueConfigFormModel } from '../dto/LeagueConfigFormDTO';
import { SeasonSchedule } from '../../domain/value-objects/SeasonSchedule';
import { SeasonScoringConfig } from '../../domain/value-objects/SeasonScoringConfig';
import { SeasonDropPolicy } from '../../domain/value-objects/SeasonDropPolicy';
import { SeasonStewardingConfig } from '../../domain/value-objects/SeasonStewardingConfig';
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 type { Weekday } from '../../domain/types/Weekday';
import { v4 as uuidv4 } from 'uuid';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';

export interface CreateSeasonForLeagueCommand {
  leagueId: string;
  name: string;
  gameId: string;
  sourceSeasonId?: string;
  /**
   * Optional high-level wizard config used to derive schedule/scoring/drop/stewarding.
   * When omitted, the Season will be created with minimal metadata only.
   */
  config?: LeagueConfigFormModel;
}

export interface CreateSeasonForLeagueResultDTO {
  seasonId: string;
}

type CreateSeasonForLeagueErrorCode = 'LEAGUE_NOT_FOUND' | 'SOURCE_SEASON_NOT_FOUND';

/**
 * CreateSeasonForLeagueUseCase
 *
 * Creates a new Season for an existing League, optionally cloning or deriving
 * configuration from a source Season or a league config form.
 */
export class CreateSeasonForLeagueUseCase {
  constructor(
    private readonly leagueRepository: ILeagueRepository,
    private readonly seasonRepository: ISeasonRepository,
  ) {}

  async execute(
    command: CreateSeasonForLeagueCommand,
  ): Promise<Result<CreateSeasonForLeagueResultDTO, ApplicationErrorCode<CreateSeasonForLeagueErrorCode>>> {
    const league = await this.leagueRepository.findById(command.leagueId);
    if (!league) {
      return Result.err({
        code: 'LEAGUE_NOT_FOUND',
        details: { message: `League not found: ${command.leagueId}` },
      });
    }

    let baseSeasonProps: {
      schedule?: SeasonSchedule;
      scoringConfig?: SeasonScoringConfig;
      dropPolicy?: SeasonDropPolicy;
      stewardingConfig?: SeasonStewardingConfig;
      maxDrivers?: number;
    } = {};

    if (command.sourceSeasonId) {
      const source = await this.seasonRepository.findById(command.sourceSeasonId);
      if (!source) {
        return Result.err({
          code: 'SOURCE_SEASON_NOT_FOUND',
          details: { message: `Source Season not found: ${command.sourceSeasonId}` },
        });
      }
      baseSeasonProps = {
        ...(source.schedule !== undefined ? { schedule: source.schedule } : {}),
        ...(source.scoringConfig !== undefined
          ? { scoringConfig: source.scoringConfig }
          : {}),
        ...(source.dropPolicy !== undefined ? { dropPolicy: source.dropPolicy } : {}),
        ...(source.stewardingConfig !== undefined
          ? { stewardingConfig: source.stewardingConfig }
          : {}),
        ...(source.maxDrivers !== undefined ? { maxDrivers: source.maxDrivers } : {}),
      };
    } else if (command.config) {
      baseSeasonProps = this.deriveSeasonPropsFromConfig(command.config);
    }

    const seasonId = uuidv4();

    const season = Season.create({
      id: seasonId,
      leagueId: league.id,
      gameId: command.gameId,
      name: command.name,
      year: new Date().getFullYear(),
      status: 'planned',
      ...(baseSeasonProps?.schedule
        ? { schedule: baseSeasonProps.schedule }
        : {}),
      ...(baseSeasonProps?.scoringConfig
        ? { scoringConfig: baseSeasonProps.scoringConfig }
        : {}),
      ...(baseSeasonProps?.dropPolicy
        ? { dropPolicy: baseSeasonProps.dropPolicy }
        : {}),
      ...(baseSeasonProps?.stewardingConfig
        ? { stewardingConfig: baseSeasonProps.stewardingConfig }
        : {}),
      ...(baseSeasonProps?.maxDrivers !== undefined
        ? { maxDrivers: baseSeasonProps.maxDrivers }
        : {}),
    });

    await this.seasonRepository.add(season);

    return Result.ok({ seasonId });
  }

  private deriveSeasonPropsFromConfig(config: LeagueConfigFormModel): {
    schedule?: SeasonSchedule;
    scoringConfig?: SeasonScoringConfig;
    dropPolicy?: SeasonDropPolicy;
    stewardingConfig?: SeasonStewardingConfig;
    maxDrivers?: number;
  } {
    const schedule = this.buildScheduleFromTimings(config);
    const scoringConfig = new SeasonScoringConfig({
      scoringPresetId: config.scoring.patternId ?? 'custom',
      customScoringEnabled: config.scoring.customScoringEnabled ?? false,
    });
    const dropPolicy = new SeasonDropPolicy({
      strategy: config.dropPolicy.strategy,
      ...(config.dropPolicy.n !== undefined ? { n: config.dropPolicy.n } : {}),
    });
    const stewardingConfig = new SeasonStewardingConfig({
      decisionMode: config.stewarding.decisionMode,
      ...(config.stewarding.requiredVotes !== undefined
        ? { requiredVotes: config.stewarding.requiredVotes }
        : {}),
      requireDefense: config.stewarding.requireDefense,
      defenseTimeLimit: config.stewarding.defenseTimeLimit,
      voteTimeLimit: config.stewarding.voteTimeLimit,
      protestDeadlineHours: config.stewarding.protestDeadlineHours,
      stewardingClosesHours: config.stewarding.stewardingClosesHours,
      notifyAccusedOnProtest: config.stewarding.notifyAccusedOnProtest,
      notifyOnVoteRequired: config.stewarding.notifyOnVoteRequired,
    });

    const structure = config.structure;
    const maxDrivers =
      typeof structure.maxDrivers === 'number' && structure.maxDrivers > 0
        ? structure.maxDrivers
        : undefined;

    return {
      ...(schedule !== undefined ? { schedule } : {}),
      scoringConfig,
      dropPolicy,
      stewardingConfig,
      ...(maxDrivers !== undefined ? { maxDrivers } : {}),
    };
  }

  private buildScheduleFromTimings(
    config: LeagueConfigFormModel,
  ): SeasonSchedule | undefined {
    const { timings } = config;
    if (!timings.seasonStartDate || !timings.raceStartTime) {
      return undefined;
    }

    const startDate = new Date(timings.seasonStartDate);
    const timeOfDay = RaceTimeOfDay.fromString(timings.raceStartTime);
    const timezoneId = timings.timezoneId ?? 'UTC';
    const timezone = new LeagueTimezone(timezoneId);

    const plannedRounds =
      typeof timings.roundsPlanned === 'number' && timings.roundsPlanned > 0
        ? timings.roundsPlanned
        : timings.sessionCount;

    const recurrence = (() => {
      const weekdays: WeekdaySet =
        timings.weekdays && timings.weekdays.length > 0
          ? WeekdaySet.fromArray(
              timings.weekdays as unknown as Weekday[],
            )
          : WeekdaySet.fromArray(['Mon']);
      switch (timings.recurrenceStrategy) {
        case 'everyNWeeks':
          return RecurrenceStrategyFactory.everyNWeeks(
            timings.intervalWeeks ?? 2,
            weekdays,
          );
        case 'monthlyNthWeekday': {
          const pattern = new MonthlyRecurrencePattern({
            ordinal: (timings.monthlyOrdinal ?? 1) as 1 | 2 | 3 | 4,
            weekday: (timings.monthlyWeekday ?? 'Mon') as Weekday,
          });
          return RecurrenceStrategyFactory.monthlyNthWeekday(pattern);
        }
        case 'weekly':
        default:
          return RecurrenceStrategyFactory.weekly(weekdays);
      }
    })();

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