/**
 * Domain Entity: RaceEvent (Aggregate Root)
 *
 * Represents a race event containing multiple sessions (practice, quali, race).
 * Immutable aggregate root with factory methods and domain validation.
 */

import { RacingDomainValidationError, RacingDomainInvariantError } from '../errors/RacingDomainError';
import type { IEntity } from '@core/shared/domain';
import type { Session } from './Session';
import { SessionType } from '../value-objects/SessionType';

export type RaceEventStatus = 'scheduled' | 'in_progress' | 'awaiting_stewarding' | 'closed' | 'cancelled';

export class RaceEvent implements IEntity<string> {
  readonly id: string;
  readonly seasonId: string;
  readonly leagueId: string;
  readonly name: string;
  readonly sessions: readonly Session[];
  readonly status: RaceEventStatus;
  readonly stewardingClosesAt: Date | undefined;

  private constructor(props: {
    id: string;
    seasonId: string;
    leagueId: string;
    name: string;
    sessions: readonly Session[];
    status: RaceEventStatus;
    stewardingClosesAt?: Date;
  }) {
    this.id = props.id;
    this.seasonId = props.seasonId;
    this.leagueId = props.leagueId;
    this.name = props.name;
    this.sessions = props.sessions;
    this.status = props.status;
    this.stewardingClosesAt = props.stewardingClosesAt;
  }

  /**
   * Factory method to create a new RaceEvent entity
   */
  static create(props: {
    id: string;
    seasonId: string;
    leagueId: string;
    name: string;
    sessions: Session[];
    status?: RaceEventStatus;
    stewardingClosesAt?: Date;
  }): RaceEvent {
    this.validate(props);

    return new RaceEvent({
      id: props.id,
      seasonId: props.seasonId,
      leagueId: props.leagueId,
      name: props.name,
      sessions: [...props.sessions], // Create immutable copy
      status: props.status ?? 'scheduled',
      ...(props.stewardingClosesAt !== undefined ? { stewardingClosesAt: props.stewardingClosesAt } : {}),
    });
  }

  /**
   * Domain validation logic
   */
  private static validate(props: {
    id: string;
    seasonId: string;
    leagueId: string;
    name: string;
    sessions: Session[];
  }): void {
    if (!props.id || props.id.trim().length === 0) {
      throw new RacingDomainValidationError('RaceEvent ID is required');
    }

    if (!props.seasonId || props.seasonId.trim().length === 0) {
      throw new RacingDomainValidationError('Season ID is required');
    }

    if (!props.leagueId || props.leagueId.trim().length === 0) {
      throw new RacingDomainValidationError('League ID is required');
    }

    if (!props.name || props.name.trim().length === 0) {
      throw new RacingDomainValidationError('RaceEvent name is required');
    }

    if (!props.sessions || props.sessions.length === 0) {
      throw new RacingDomainValidationError('RaceEvent must have at least one session');
    }

    // Validate all sessions belong to this race event
    const invalidSessions = props.sessions.filter(s => s.raceEventId !== props.id);
    if (invalidSessions.length > 0) {
      throw new RacingDomainValidationError('All sessions must belong to this race event');
    }

    // Validate session types are unique
    const sessionTypes = props.sessions.map(s => s.sessionType.value);
    const uniqueTypes = new Set(sessionTypes);
    if (uniqueTypes.size !== sessionTypes.length) {
      throw new RacingDomainValidationError('Session types must be unique within a race event');
    }

    // Validate at least one main race session exists
    const hasMainRace = props.sessions.some(s => s.sessionType.value === 'main');
    if (!hasMainRace) {
      throw new RacingDomainValidationError('RaceEvent must have at least one main race session');
    }
  }

  /**
   * Start the race event (move from scheduled to in_progress)
   */
  start(): RaceEvent {
    if (this.status !== 'scheduled') {
      throw new RacingDomainInvariantError('Only scheduled race events can be started');
    }

    return RaceEvent.create({
      id: this.id,
      seasonId: this.seasonId,
      leagueId: this.leagueId,
      name: this.name,
      sessions: [...this.sessions],
      status: 'in_progress',
      ...(this.stewardingClosesAt !== undefined ? { stewardingClosesAt: this.stewardingClosesAt } : {}),
    });
  }

  /**
   * Complete the main race session and move to awaiting_stewarding
   */
  completeMainRace(): RaceEvent {
    if (this.status !== 'in_progress') {
      throw new RacingDomainInvariantError('Only in-progress race events can complete main race');
    }

    const mainRaceSession = this.getMainRaceSession();
    if (!mainRaceSession || mainRaceSession.status !== 'completed') {
      throw new RacingDomainInvariantError('Main race session must be completed first');
    }

    return RaceEvent.create({
      id: this.id,
      seasonId: this.seasonId,
      leagueId: this.leagueId,
      name: this.name,
      sessions: [...this.sessions],
      status: 'awaiting_stewarding',
      ...(this.stewardingClosesAt !== undefined ? { stewardingClosesAt: this.stewardingClosesAt } : {}),
    });
  }

  /**
   * Close stewarding and finalize the race event
   */
  closeStewarding(): RaceEvent {
    if (this.status !== 'awaiting_stewarding') {
      throw new RacingDomainInvariantError('Only race events awaiting stewarding can be closed');
    }

    return RaceEvent.create({
      id: this.id,
      seasonId: this.seasonId,
      leagueId: this.leagueId,
      name: this.name,
      sessions: [...this.sessions],
      status: 'closed',
      ...(this.stewardingClosesAt !== undefined ? { stewardingClosesAt: this.stewardingClosesAt } : {}),
    });
  }

  /**
   * Cancel the race event
   */
  cancel(): RaceEvent {
    if (this.status === 'closed') {
      throw new RacingDomainInvariantError('Cannot cancel a closed race event');
    }

    if (this.status === 'cancelled') {
      return this;
    }

    return RaceEvent.create({
      id: this.id,
      seasonId: this.seasonId,
      leagueId: this.leagueId,
      name: this.name,
      sessions: [...this.sessions],
      status: 'cancelled',
      ...(this.stewardingClosesAt !== undefined ? { stewardingClosesAt: this.stewardingClosesAt } : {}),
    });
  }

  /**
   * Get the main race session (the one that counts for championship points)
   */
  getMainRaceSession(): Session | undefined {
    return this.sessions.find(s => s.sessionType.equals(SessionType.main()));
  }

  /**
   * Get all sessions of a specific type
   */
  getSessionsByType(sessionType: SessionType): Session[] {
    return this.sessions.filter(s => s.sessionType.equals(sessionType));
  }

  /**
   * Get all completed sessions
   */
  getCompletedSessions(): Session[] {
    return this.sessions.filter(s => s.status === 'completed');
  }

  /**
   * Check if all sessions are completed
   */
  areAllSessionsCompleted(): boolean {
    return this.sessions.every(s => s.status === 'completed');
  }

  /**
   * Check if the main race is completed
   */
  isMainRaceCompleted(): boolean {
    const mainRace = this.getMainRaceSession();
    return mainRace ? mainRace.status === 'completed' : false;
  }

  /**
   * Check if stewarding window has expired
   */
  hasStewardingExpired(): boolean {
    if (!this.stewardingClosesAt) return false;
    return new Date() > this.stewardingClosesAt;
  }

  /**
   * Check if race event is in the past
   */
  isPast(): boolean {
    const latestSession = this.sessions.reduce((latest, session) =>
      session.scheduledAt > latest.scheduledAt ? session : latest
    );
    return latestSession.scheduledAt < new Date();
  }

  /**
   * Check if race event is upcoming
   */
  isUpcoming(): boolean {
    return this.status === 'scheduled' && !this.isPast();
  }

  /**
   * Check if race event is currently running
   */
  isLive(): boolean {
    return this.status === 'in_progress';
  }

  /**
   * Check if race event is awaiting stewarding decisions
   */
  isAwaitingStewarding(): boolean {
    return this.status === 'awaiting_stewarding';
  }

  /**
   * Check if race event is closed (stewarding complete)
   */
  isClosed(): boolean {
    return this.status === 'closed';
  }
}