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 { normalizeVisibility } from '../dto/LeagueConfigFormDTO';
import { LeagueVisibility } from '../../domain/value-objects/LeagueVisibility';
import { v4 as uuidv4 } from 'uuid';

/**
 * DTOs and helpers shared across Season-focused use cases.
 */

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;
}

export interface SeasonSummaryDTO {
  seasonId: string;
  leagueId: string;
  name: string;
  status: import('../../domain/entities/Season').SeasonStatus;
  startDate?: Date;
  endDate?: Date;
  isPrimary: boolean;
}

export interface ListSeasonsForLeagueQuery {
  leagueId: string;
}

export interface ListSeasonsForLeagueResultDTO {
  items: SeasonSummaryDTO[];
}

export interface GetSeasonDetailsQuery {
  leagueId: string;
  seasonId: string;
}

export interface SeasonDetailsDTO {
  seasonId: string;
  leagueId: string;
  gameId: string;
  name: string;
  status: import('../../domain/entities/Season').SeasonStatus;
  startDate?: Date;
  endDate?: Date;
  maxDrivers?: number;
  schedule?: {
    startDate: Date;
    plannedRounds: number;
  };
  scoring?: {
    scoringPresetId: string;
    customScoringEnabled: boolean;
  };
  dropPolicy?: {
    strategy: import('../../domain/value-objects/SeasonDropPolicy').SeasonDropStrategy;
    n?: number;
  };
  stewarding?: {
    decisionMode: import('../../domain/entities/League').StewardingDecisionMode;
    requiredVotes?: number;
    requireDefense: boolean;
    defenseTimeLimit: number;
    voteTimeLimit: number;
    protestDeadlineHours: number;
    stewardingClosesHours: number;
    notifyAccusedOnProtest: boolean;
    notifyOnVoteRequired: boolean;
  };
}

export type SeasonLifecycleTransition =
  | 'activate'
  | 'complete'
  | 'archive'
  | 'cancel';

export interface ManageSeasonLifecycleCommand {
  leagueId: string;
  seasonId: string;
  transition: SeasonLifecycleTransition;
}

export interface ManageSeasonLifecycleResultDTO {
  seasonId: string;
  status: import('../../domain/entities/Season').SeasonStatus;
  startDate?: Date;
  endDate?: Date;
}

/**
 * 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<CreateSeasonForLeagueResultDTO> {
    const league = await this.leagueRepository.findById(command.leagueId);
    if (!league) {
      throw new Error(`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) {
        throw new Error(`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 { 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,
    });
  }
}

/**
 * ListSeasonsForLeagueUseCase
 */
export class ListSeasonsForLeagueUseCase {
  constructor(
    private readonly leagueRepository: ILeagueRepository,
    private readonly seasonRepository: ISeasonRepository,
  ) {}

  async execute(
    query: ListSeasonsForLeagueQuery,
  ): Promise<ListSeasonsForLeagueResultDTO> {
    const league = await this.leagueRepository.findById(query.leagueId);
    if (!league) {
      throw new Error(`League not found: ${query.leagueId}`);
    }

    const seasons = await this.seasonRepository.listByLeague(league.id);
    const items: SeasonSummaryDTO[] = seasons.map((s) => ({
      seasonId: s.id,
      leagueId: s.leagueId,
      name: s.name,
      status: s.status,
      ...(s.startDate !== undefined ? { startDate: s.startDate } : {}),
      ...(s.endDate !== undefined ? { endDate: s.endDate } : {}),
      // League currently does not track primarySeasonId, so mark false for now.
      isPrimary: false,
    }));

    return { items };
  }
}

/**
 * GetSeasonDetailsUseCase
 */
export class GetSeasonDetailsUseCase {
  constructor(
    private readonly leagueRepository: ILeagueRepository,
    private readonly seasonRepository: ISeasonRepository,
  ) {}

  async execute(query: GetSeasonDetailsQuery): Promise<SeasonDetailsDTO> {
    const league = await this.leagueRepository.findById(query.leagueId);
    if (!league) {
      throw new Error(`League not found: ${query.leagueId}`);
    }

    const season = await this.seasonRepository.findById(query.seasonId);
    if (!season || season.leagueId !== league.id) {
      throw new Error(
        `Season ${query.seasonId} does not belong to league ${league.id}`,
      );
    }

    return {
      seasonId: season.id,
      leagueId: season.leagueId,
      gameId: season.gameId,
      name: season.name,
      status: season.status,
      ...(season.startDate !== undefined ? { startDate: season.startDate } : {}),
      ...(season.endDate !== undefined ? { endDate: season.endDate } : {}),
      ...(season.maxDrivers !== undefined ? { maxDrivers: season.maxDrivers } : {}),
      ...(season.schedule
        ? {
            schedule: {
              startDate: season.schedule.startDate,
              plannedRounds: season.schedule.plannedRounds,
            },
          }
        : {}),
      ...(season.scoringConfig
        ? {
            scoring: {
              scoringPresetId: season.scoringConfig.scoringPresetId,
              customScoringEnabled:
                season.scoringConfig.customScoringEnabled ?? false,
            },
          }
        : {}),
      ...(season.dropPolicy
        ? {
            dropPolicy: {
              strategy: season.dropPolicy.strategy,
              ...(season.dropPolicy.n !== undefined
                ? { n: season.dropPolicy.n }
                : {}),
            },
          }
        : {}),
      ...(season.stewardingConfig
        ? {
            stewarding: {
              decisionMode: season.stewardingConfig.decisionMode,
              ...(season.stewardingConfig.requiredVotes !== undefined
                ? { requiredVotes: season.stewardingConfig.requiredVotes }
                : {}),
              requireDefense: season.stewardingConfig.requireDefense,
              defenseTimeLimit: season.stewardingConfig.defenseTimeLimit,
              voteTimeLimit: season.stewardingConfig.voteTimeLimit,
              protestDeadlineHours:
                season.stewardingConfig.protestDeadlineHours,
              stewardingClosesHours:
                season.stewardingConfig.stewardingClosesHours,
              notifyAccusedOnProtest:
                season.stewardingConfig.notifyAccusedOnProtest,
              notifyOnVoteRequired:
                season.stewardingConfig.notifyOnVoteRequired,
            },
          }
        : {}),
    };
  }
}

/**
 * ManageSeasonLifecycleUseCase
 */
export class ManageSeasonLifecycleUseCase {
  constructor(
    private readonly leagueRepository: ILeagueRepository,
    private readonly seasonRepository: ISeasonRepository,
  ) {}

  async execute(
    command: ManageSeasonLifecycleCommand,
  ): Promise<ManageSeasonLifecycleResultDTO> {
    const league = await this.leagueRepository.findById(command.leagueId);
    if (!league) {
      throw new Error(`League not found: ${command.leagueId}`);
    }

    const season = await this.seasonRepository.findById(command.seasonId);
    if (!season || season.leagueId !== league.id) {
      throw new Error(
        `Season ${command.seasonId} does not belong to league ${league.id}`,
      );
    }

    let updated: Season;
    switch (command.transition) {
      case 'activate':
        updated = season.activate();
        break;
      case 'complete':
        updated = season.complete();
        break;
      case 'archive':
        updated = season.archive();
        break;
      case 'cancel':
        updated = season.cancel();
        break;
      default:
        throw new Error(`Unsupported Season lifecycle transition`);
    }

    await this.seasonRepository.update(updated);

    return {
      seasonId: updated.id,
      status: updated.status,
      ...(updated.startDate !== undefined ? { startDate: updated.startDate } : {}),
      ...(updated.endDate !== undefined ? { endDate: updated.endDate } : {}),
    };
  }
}