/**
 * Domain Entity: Race
 *
 * Represents a race/session in the GridPilot platform.
 * Immutable entity with factory methods and domain validation.
 */
 
import { RacingDomainValidationError, RacingDomainInvariantError } from '../errors/RacingDomainError';
import type { IEntity } from '@core/shared/domain';
import { SessionType } from '../value-objects/SessionType';
export type RaceStatus = 'scheduled' | 'running' | 'completed' | 'cancelled';
 
export class Race implements IEntity<string> {
  readonly id: string;
  readonly leagueId: string;
  readonly scheduledAt: Date;
  readonly track: string;
  readonly trackId: string | undefined;
  readonly car: string;
  readonly carId: string | undefined;
  readonly sessionType: SessionType;
  readonly status: RaceStatus;
  readonly strengthOfField: number | undefined;
  readonly registeredCount: number | undefined;
  readonly maxParticipants: number | undefined;

  private constructor(props: {
    id: string;
    leagueId: string;
    scheduledAt: Date;
    track: string;
    trackId?: string;
    car: string;
    carId?: string;
    sessionType: SessionType;
    status: RaceStatus;
    strengthOfField?: number;
    registeredCount?: number;
    maxParticipants?: number;
  }) {
    this.id = props.id;
    this.leagueId = props.leagueId;
    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 Race entity
   */
  static create(props: {
    id: string;
    leagueId: string;
    scheduledAt: Date;
    track: string;
    trackId?: string;
    car: string;
    carId?: string;
    sessionType?: SessionType;
    status?: RaceStatus;
    strengthOfField?: number;
    registeredCount?: number;
    maxParticipants?: number;
  }): Race {
    this.validate(props);

    return new Race({
      id: props.id,
      leagueId: props.leagueId,
      scheduledAt: props.scheduledAt,
      track: props.track,
      ...(props.trackId !== undefined ? { trackId: props.trackId } : {}),
      car: props.car,
      ...(props.carId !== undefined ? { carId: props.carId } : {}),
      sessionType: props.sessionType ?? SessionType.main(),
      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;
    leagueId: string;
    scheduledAt: Date;
    track: string;
    car: string;
  }): void {
    if (!props.id || props.id.trim().length === 0) {
      throw new RacingDomainValidationError('Race ID is required');
    }

    if (!props.leagueId || props.leagueId.trim().length === 0) {
      throw new RacingDomainValidationError('League 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');
    }
  }

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

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

    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 Race.create(props);
  }

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

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

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

    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 Race.create(props);
  }

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

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

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

    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 Race.create(props);
  }

  /**
   * Update SOF and participant count
   */
  updateField(strengthOfField: number, registeredCount: number): Race {
    const base = {
      id: this.id,
      leagueId: this.leagueId,
      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 Race.create(props);
  }

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

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

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