/**
 * 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';
import { RaceStatus, RaceStatusValue } from '../value-objects/RaceStatus';
import { ParticipantCount } from '../value-objects/ParticipantCount';
import { MaxParticipants } from '../value-objects/MaxParticipants';
import { StrengthOfField } from '../value-objects/StrengthOfField';
   
export type { RaceStatus, RaceStatusValue } from '../value-objects/RaceStatus';

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: StrengthOfField | undefined;
  readonly registeredCount: ParticipantCount | undefined;
  readonly maxParticipants: MaxParticipants | undefined;

  // Compatibility properties for existing code
  get statusString(): string {
    return this.status.toString();
  }

  get strengthOfFieldNumber(): number | undefined {
    return this.strengthOfField ? this.strengthOfField.toNumber() : undefined;
  }

  get registeredCountNumber(): number | undefined {
    return this.registeredCount ? this.registeredCount.toNumber() : undefined;
  }

  get maxParticipantsNumber(): number | undefined {
    return this.maxParticipants ? this.maxParticipants.toNumber() : undefined;
  }

  private constructor(props: {
    id: string;
    leagueId: string;
    scheduledAt: Date;
    track: string;
    trackId?: string;
    car: string;
    carId?: string;
    sessionType: SessionType;
    status: RaceStatus;
    strengthOfField?: StrengthOfField;
    registeredCount?: ParticipantCount;
    maxParticipants?: MaxParticipants;
  }) {
    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
   * Enforces all business rules and invariants
   */
  static create(props: {
    id: string;
    leagueId: string;
    scheduledAt: Date;
    track: string;
    trackId?: string;
    car: string;
    carId?: string;
    sessionType?: SessionType;
    status?: RaceStatusValue;
    strengthOfField?: number;
    registeredCount?: number;
    maxParticipants?: number;
  }): Race {
    // Validate required fields
    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) || isNaN(props.scheduledAt.getTime())) {
      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');
    }

    // Validate status
    const status = RaceStatus.create(props.status ?? 'scheduled');

    // Validate participant counts
    let registeredCount: ParticipantCount | undefined;
    let maxParticipants: MaxParticipants | undefined;

    if (props.registeredCount !== undefined) {
      registeredCount = ParticipantCount.create(props.registeredCount);
    }

    if (props.maxParticipants !== undefined) {
      maxParticipants = MaxParticipants.create(props.maxParticipants);
      
      // Validate that registered count doesn't exceed max
      if (registeredCount && !maxParticipants.canAccommodate(registeredCount.toNumber())) {
        throw new RacingDomainValidationError(
          `Registered count (${registeredCount.toNumber()}) exceeds max participants (${maxParticipants.toNumber()})`
        );
      }
    }

    // Validate strength of field if provided
    let strengthOfField: StrengthOfField | undefined;
    if (props.strengthOfField !== undefined) {
      strengthOfField = StrengthOfField.create(props.strengthOfField);
    }

    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,
      ...(strengthOfField !== undefined ? { strengthOfField } : {}),
      ...(registeredCount !== undefined ? { registeredCount } : {}),
      ...(maxParticipants !== undefined ? { maxParticipants } : {}),
    });
  }

  static rehydrate(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 {
    let registeredCount: ParticipantCount | undefined;
    let maxParticipants: MaxParticipants | undefined;

    if (props.registeredCount !== undefined) {
      registeredCount = ParticipantCount.create(props.registeredCount);
    }

    if (props.maxParticipants !== undefined) {
      maxParticipants = MaxParticipants.create(props.maxParticipants);

      if (registeredCount && !maxParticipants.canAccommodate(registeredCount.toNumber())) {
        throw new RacingDomainValidationError(
          `Registered count (${registeredCount.toNumber()}) exceeds max participants (${maxParticipants.toNumber()})`,
        );
      }
    }

    let strengthOfField: StrengthOfField | undefined;
    if (props.strengthOfField !== undefined) {
      strengthOfField = StrengthOfField.create(props.strengthOfField);
    }

    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,
      status: props.status,
      ...(strengthOfField !== undefined ? { strengthOfField } : {}),
      ...(registeredCount !== undefined ? { registeredCount } : {}),
      ...(maxParticipants !== undefined ? { maxParticipants } : {}),
    });
  }
 
  /**
   * Start the race (move from scheduled to running)
   */
  start(): Race {
    const transition = this.status.canTransitionTo('running');
    if (!transition.valid) {
      throw new RacingDomainInvariantError(transition.error!);
    }

    return Race.create({
      id: this.id,
      leagueId: this.leagueId,
      scheduledAt: this.scheduledAt,
      track: this.track,
      ...(this.trackId !== undefined ? { trackId: this.trackId } : {}),
      car: this.car,
      ...(this.carId !== undefined ? { carId: this.carId } : {}),
      sessionType: this.sessionType,
      status: 'running',
      ...(this.strengthOfField !== undefined ? { strengthOfField: this.strengthOfField.toNumber() } : {}),
      ...(this.registeredCount !== undefined ? { registeredCount: this.registeredCount.toNumber() } : {}),
      ...(this.maxParticipants !== undefined ? { maxParticipants: this.maxParticipants.toNumber() } : {}),
    });
  }

  /**
   * Mark race as completed
   */
  complete(): Race {
    const transition = this.status.canTransitionTo('completed');
    if (!transition.valid) {
      throw new RacingDomainInvariantError(transition.error!);
    }

    return Race.create({
      id: this.id,
      leagueId: this.leagueId,
      scheduledAt: this.scheduledAt,
      track: this.track,
      ...(this.trackId !== undefined ? { trackId: this.trackId } : {}),
      car: this.car,
      ...(this.carId !== undefined ? { carId: this.carId } : {}),
      sessionType: this.sessionType,
      status: 'completed',
      ...(this.strengthOfField !== undefined ? { strengthOfField: this.strengthOfField.toNumber() } : {}),
      ...(this.registeredCount !== undefined ? { registeredCount: this.registeredCount.toNumber() } : {}),
      ...(this.maxParticipants !== undefined ? { maxParticipants: this.maxParticipants.toNumber() } : {}),
    });
  }

  /**
   * Cancel the race
   */
  cancel(): Race {
    if (this.status.isCancelled()) {
      throw new RacingDomainInvariantError('Race is already cancelled');
    }

    if (this.status.isCompleted()) {
      throw new RacingDomainInvariantError('Cannot cancel completed race');
    }

    const transition = this.status.canTransitionTo('cancelled');
    if (!transition.valid) {
      throw new RacingDomainInvariantError(transition.error!);
    }

    return Race.create({
      id: this.id,
      leagueId: this.leagueId,
      scheduledAt: this.scheduledAt,
      track: this.track,
      ...(this.trackId !== undefined ? { trackId: this.trackId } : {}),
      car: this.car,
      ...(this.carId !== undefined ? { carId: this.carId } : {}),
      sessionType: this.sessionType,
      status: 'cancelled',
      ...(this.strengthOfField !== undefined
        ? { strengthOfField: this.strengthOfField.toNumber() }
        : {}),
      ...(this.registeredCount !== undefined
        ? { registeredCount: this.registeredCount.toNumber() }
        : {}),
      ...(this.maxParticipants !== undefined
        ? { maxParticipants: this.maxParticipants.toNumber() }
        : {}),
    });
  }

  /**
   * Re-open a previously completed or cancelled race
   */
  reopen(): Race {
    if (this.status.isScheduled()) {
      throw new RacingDomainInvariantError('Race is already scheduled');
    }

    if (this.status.isRunning()) {
      throw new RacingDomainInvariantError('Cannot reopen running race');
    }

    const transition = this.status.canTransitionTo('scheduled');
    if (!transition.valid) {
      throw new RacingDomainInvariantError(transition.error!);
    }

    return Race.create({
      id: this.id,
      leagueId: this.leagueId,
      scheduledAt: this.scheduledAt,
      track: this.track,
      ...(this.trackId !== undefined ? { trackId: this.trackId } : {}),
      car: this.car,
      ...(this.carId !== undefined ? { carId: this.carId } : {}),
      sessionType: this.sessionType,
      status: 'scheduled',
      ...(this.strengthOfField !== undefined
        ? { strengthOfField: this.strengthOfField.toNumber() }
        : {}),
      ...(this.registeredCount !== undefined
        ? { registeredCount: this.registeredCount.toNumber() }
        : {}),
      ...(this.maxParticipants !== undefined
        ? { maxParticipants: this.maxParticipants.toNumber() }
        : {}),
    });
  }

  /**
   * Update SOF and participant count
   */
  updateField(strengthOfField: number, registeredCount: number): Race {
    // Validate strength of field
    const newStrengthOfField = StrengthOfField.create(strengthOfField);

    // Validate registered count against max participants
    const newRegisteredCount = ParticipantCount.create(registeredCount);
    if (this.maxParticipants && !this.maxParticipants.canAccommodate(newRegisteredCount.toNumber())) {
      throw new RacingDomainValidationError(
        `Registered count (${newRegisteredCount.toNumber()}) exceeds max participants (${this.maxParticipants.toNumber()})`
      );
    }

    return Race.create({
      id: this.id,
      leagueId: this.leagueId,
      scheduledAt: this.scheduledAt,
      track: this.track,
      ...(this.trackId !== undefined ? { trackId: this.trackId } : {}),
      car: this.car,
      ...(this.carId !== undefined ? { carId: this.carId } : {}),
      sessionType: this.sessionType,
      status: this.status.toString(),
      strengthOfField: newStrengthOfField.toNumber(),
      registeredCount: newRegisteredCount.toNumber(),
      ...(this.maxParticipants !== undefined ? { maxParticipants: this.maxParticipants.toNumber() } : {}),
    });
  }

  /**
   * Add a participant to the race
   */
  addParticipant(): Race {
    if (!this.registeredCount) {
      throw new RacingDomainInvariantError('Race must have a registered count initialized');
    }

    const newCount = this.registeredCount.increment();

    if (this.maxParticipants && !this.maxParticipants.canAccommodate(newCount.toNumber())) {
      throw new RacingDomainInvariantError(
        `Cannot add participant: race capacity (${this.maxParticipants.toNumber()}) would be exceeded`
      );
    }

    return Race.create({
      id: this.id,
      leagueId: this.leagueId,
      scheduledAt: this.scheduledAt,
      track: this.track,
      ...(this.trackId !== undefined ? { trackId: this.trackId } : {}),
      car: this.car,
      ...(this.carId !== undefined ? { carId: this.carId } : {}),
      sessionType: this.sessionType,
      status: this.status.toString(),
      ...(this.strengthOfField !== undefined ? { strengthOfField: this.strengthOfField.toNumber() } : {}),
      registeredCount: newCount.toNumber(),
      ...(this.maxParticipants !== undefined ? { maxParticipants: this.maxParticipants.toNumber() } : {}),
    });
  }

  /**
   * Remove a participant from the race
   */
  removeParticipant(): Race {
    if (!this.registeredCount) {
      throw new RacingDomainInvariantError('Race must have a registered count initialized');
    }

    if (this.registeredCount.isZero()) {
      throw new RacingDomainInvariantError('Cannot remove participant: race has no participants');
    }

    const newCount = this.registeredCount.decrement();

    return Race.create({
      id: this.id,
      leagueId: this.leagueId,
      scheduledAt: this.scheduledAt,
      track: this.track,
      ...(this.trackId !== undefined ? { trackId: this.trackId } : {}),
      car: this.car,
      ...(this.carId !== undefined ? { carId: this.carId } : {}),
      sessionType: this.sessionType,
      status: this.status.toString(),
      ...(this.strengthOfField !== undefined ? { strengthOfField: this.strengthOfField.toNumber() } : {}),
      registeredCount: newCount.toNumber(),
      ...(this.maxParticipants !== undefined ? { maxParticipants: this.maxParticipants.toNumber() } : {}),
    });
  }

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

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

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

  /**
   * Check if race can accept more participants
   */
  canAcceptMore(): boolean {
    if (!this.maxParticipants || !this.registeredCount) return false;
    return this.maxParticipants.canAccommodate(this.registeredCount.toNumber() + 1);
  }

  /**
   * Get current registered count
   */
  getRegisteredCount(): number {
    return this.registeredCount ? this.registeredCount.toNumber() : 0;
  }

  /**
   * Get max participants
   */
  getMaxParticipants(): number | undefined {
    return this.maxParticipants ? this.maxParticipants.toNumber() : undefined;
  }

  /**
   * Get strength of field as number
   */
  getStrengthOfField(): number | undefined {
    return this.strengthOfField ? this.strengthOfField.toNumber() : undefined;
  }

  /**
   * Get status as string (for compatibility with existing code)
   */
  getStatus(): string {
    return this.status.toString();
  }
}