/**
 * Domain Entity: Session
 *
 * Represents a racing session within a race event.
 * Immutable entity with factory methods and domain validation.
 */

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

export type SessionStatus = 'scheduled' | 'running' | 'completed' | 'cancelled';

export class Session extends Entity<string> {
  readonly raceEventId: string;
  readonly scheduledAt: Date;
  readonly track: string;
  readonly trackId: string | undefined;
  readonly car: string;
  readonly carId: string | undefined;
  readonly sessionType: SessionType;
  readonly status: SessionStatus;
  readonly strengthOfField: number | undefined;
  readonly registeredCount: number | undefined;
  readonly maxParticipants: number | undefined;

  private constructor(props: {
    id: string;
    raceEventId: string;
    scheduledAt: Date;
    track: string;
    trackId?: string;
    car: string;
    carId?: string;
    sessionType: SessionType;
    status: SessionStatus;
    strengthOfField?: number;
    registeredCount?: number;
    maxParticipants?: number;
  }) {
    super(props.id);
    
    this.raceEventId = props.raceEventId;
    this.scheduledAt = props.scheduledAt;
    this.track = props.track;
    this.trackId = props.trackId;
    this.car = props.car;
    this.carId = props.carId;
    this.sessionType = props.sessionType;
    this.status = props.status;
    this.strengthOfField = props.strengthOfField;
    this.registeredCount = props.registeredCount;
    this.maxParticipants = props.maxParticipants;
  }

  /**
   * Factory method to create a new Session entity
   */
  static create(props: {
    id: string;
    raceEventId: string;
    scheduledAt: Date;
    track: string;
    trackId?: string;
    car: string;
    carId?: string;
    sessionType: SessionType;
    status?: SessionStatus;
    strengthOfField?: number;
    registeredCount?: number;
    maxParticipants?: number;
  }): Session {
    this.validate(props);

    return new Session({
      id: props.id,
      raceEventId: props.raceEventId,
      scheduledAt: props.scheduledAt,
      track: props.track,
      ...(props.trackId !== undefined ? { trackId: props.trackId } : {}),
      car: props.car,
      ...(props.carId !== undefined ? { carId: props.carId } : {}),
      sessionType: props.sessionType,
      status: props.status ?? 'scheduled',
      ...(props.strengthOfField !== undefined ? { strengthOfField: props.strengthOfField } : {}),
      ...(props.registeredCount !== undefined ? { registeredCount: props.registeredCount } : {}),
      ...(props.maxParticipants !== undefined ? { maxParticipants: props.maxParticipants } : {}),
    });
  }

  /**
   * Domain validation logic
   */
  private static validate(props: {
    id: string;
    raceEventId: string;
    scheduledAt: Date;
    track: string;
    car: string;
    sessionType: SessionType;
  }): void {
    if (!props.id || props.id.trim().length === 0) {
      throw new RacingDomainValidationError('Session ID is required');
    }

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

    if (!props.scheduledAt || !(props.scheduledAt instanceof Date)) {
      throw new RacingDomainValidationError('Valid scheduled date is required');
    }

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

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

    if (!props.sessionType) {
      throw new RacingDomainValidationError('Session type is required');
    }
  }

  /**
   * Start the session (move from scheduled to running)
   */
  start(): Session {
    if (this.status !== 'scheduled') {
      throw new RacingDomainInvariantError('Only scheduled sessions can be started');
    }

    const base = {
      id: this.id,
      raceEventId: this.raceEventId,
      scheduledAt: this.scheduledAt,
      track: this.track,
      car: this.car,
      sessionType: this.sessionType,
      status: 'running' as SessionStatus,
    };

    const withTrackId =
      this.trackId !== undefined ? { ...base, trackId: this.trackId } : base;
    const withCarId =
      this.carId !== undefined ? { ...withTrackId, carId: this.carId } : withTrackId;
    const withSof =
      this.strengthOfField !== undefined
        ? { ...withCarId, strengthOfField: this.strengthOfField }
        : withCarId;
    const withRegistered =
      this.registeredCount !== undefined
        ? { ...withSof, registeredCount: this.registeredCount }
        : withSof;
    const props =
      this.maxParticipants !== undefined
        ? { ...withRegistered, maxParticipants: this.maxParticipants }
        : withRegistered;

    return Session.create(props);
  }

  /**
   * Mark session as completed
   */
  complete(): Session {
    if (this.status === 'completed') {
      throw new RacingDomainInvariantError('Session is already completed');
    }

    if (this.status === 'cancelled') {
      throw new RacingDomainInvariantError('Cannot complete a cancelled session');
    }

    const base = {
      id: this.id,
      raceEventId: this.raceEventId,
      scheduledAt: this.scheduledAt,
      track: this.track,
      car: this.car,
      sessionType: this.sessionType,
      status: 'completed' as SessionStatus,
    };

    const withTrackId =
      this.trackId !== undefined ? { ...base, trackId: this.trackId } : base;
    const withCarId =
      this.carId !== undefined ? { ...withTrackId, carId: this.carId } : withTrackId;
    const withSof =
      this.strengthOfField !== undefined
        ? { ...withCarId, strengthOfField: this.strengthOfField }
        : withCarId;
    const withRegistered =
      this.registeredCount !== undefined
        ? { ...withSof, registeredCount: this.registeredCount }
        : withSof;
    const props =
      this.maxParticipants !== undefined
        ? { ...withRegistered, maxParticipants: this.maxParticipants }
        : withRegistered;

    return Session.create(props);
  }

  /**
   * Cancel the session
   */
  cancel(): Session {
    if (this.status === 'completed') {
      throw new RacingDomainInvariantError('Cannot cancel a completed session');
    }

    if (this.status === 'cancelled') {
      throw new RacingDomainInvariantError('Session is already cancelled');
    }

    const base = {
      id: this.id,
      raceEventId: this.raceEventId,
      scheduledAt: this.scheduledAt,
      track: this.track,
      car: this.car,
      sessionType: this.sessionType,
      status: 'cancelled' as SessionStatus,
    };

    const withTrackId =
      this.trackId !== undefined ? { ...base, trackId: this.trackId } : base;
    const withCarId =
      this.carId !== undefined ? { ...withTrackId, carId: this.carId } : withTrackId;
    const withSof =
      this.strengthOfField !== undefined
        ? { ...withCarId, strengthOfField: this.strengthOfField }
        : withCarId;
    const withRegistered =
      this.registeredCount !== undefined
        ? { ...withSof, registeredCount: this.registeredCount }
        : withSof;
    const props =
      this.maxParticipants !== undefined
        ? { ...withRegistered, maxParticipants: this.maxParticipants }
        : withRegistered;

    return Session.create(props);
  }

  /**
   * Update SOF and participant count
   */
  updateField(strengthOfField: number, registeredCount: number): Session {
    const base = {
      id: this.id,
      raceEventId: this.raceEventId,
      scheduledAt: this.scheduledAt,
      track: this.track,
      car: this.car,
      sessionType: this.sessionType,
      status: this.status,
      strengthOfField,
      registeredCount,
    };

    const withTrackId =
      this.trackId !== undefined ? { ...base, trackId: this.trackId } : base;
    const withCarId =
      this.carId !== undefined ? { ...withTrackId, carId: this.carId } : withTrackId;
    const props =
      this.maxParticipants !== undefined
        ? { ...withCarId, maxParticipants: this.maxParticipants }
        : withCarId;

    return Session.create(props);
  }

  /**
   * Check if session is in the past
   */
  isPast(): boolean {
    return this.scheduledAt < new Date();
  }

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

  /**
   * Check if session is live/running
   */
  isLive(): boolean {
    return this.status === 'running';
  }

  /**
   * Check if this session counts for championship points
   */
  countsForPoints(): boolean {
    return this.sessionType.countsForPoints();
  }

  /**
   * Check if this session determines grid positions
   */
  determinesGrid(): boolean {
    return this.sessionType.determinesGrid();
  }
}