/**
 * Domain Entity: Protest
 *
 * Represents a protest filed by a driver against another driver for an incident during a race.
 *
 * Workflow states:
 * - pending: Initial state when protest is filed
 * - awaiting_defense: Defense has been requested from the accused driver
 * - under_review: Steward is actively reviewing the protest
 * - upheld: Protest was upheld (penalty will be applied)
 * - dismissed: Protest was dismissed (no action taken)
 * - withdrawn: Protesting driver withdrew the protest
 */
import { RacingDomainValidationError, RacingDomainInvariantError } from '../errors/RacingDomainError';
import type { IEntity } from '@core/shared/domain';
import { ProtestId } from './ProtestId';
import { RaceId } from './RaceId';
import { DriverId } from './DriverId';
import { StewardId } from './StewardId';
import { ProtestStatus } from './ProtestStatus';
import { ProtestIncident } from './ProtestIncident';
import { ProtestComment } from './ProtestComment';
import { VideoUrl } from './VideoUrl';
import { FiledAt } from './FiledAt';
import { ReviewedAt } from './ReviewedAt';
import { ProtestDefense } from './ProtestDefense';
import { DefenseRequestedAt } from './DefenseRequestedAt';
import { DecisionNotes } from './DecisionNotes';

export interface ProtestProps {
  id: ProtestId;
  raceId: RaceId;
  /** The driver filing the protest */
  protestingDriverId: DriverId;
  /** The driver being protested against */
  accusedDriverId: DriverId;
  /** Details of the incident */
  incident: ProtestIncident;
  /** Optional comment/statement from the protesting driver */
  comment?: ProtestComment;
  /** URL to proof video clip */
  proofVideoUrl?: VideoUrl;
  /** Current status of the protest */
  status: ProtestStatus;
  /** ID of the steward/admin who reviewed (if any) */
  reviewedBy?: StewardId;
  /** Decision notes from the steward */
  decisionNotes?: DecisionNotes;
  /** Timestamp when the protest was filed */
  filedAt: FiledAt;
  /** Timestamp when the protest was reviewed */
  reviewedAt?: ReviewedAt;
  /** Defense from the accused driver (if requested and submitted) */
  defense?: ProtestDefense;
  /** Timestamp when defense was requested */
  defenseRequestedAt?: DefenseRequestedAt;
  /** ID of the steward who requested defense */
  defenseRequestedBy?: StewardId;
}

export class Protest implements IEntity<string> {
  private constructor(private readonly props: ProtestProps) {}

  static create(props: {
    id: string;
    raceId: string;
    protestingDriverId: string;
    accusedDriverId: string;
    incident: { lap: number; description: string; timeInRace?: number };
    comment?: string;
    proofVideoUrl?: string;
    status?: string;
    reviewedBy?: string;
    decisionNotes?: string;
    filedAt?: Date;
    reviewedAt?: Date;
    defense?: { statement: string; videoUrl?: string; submittedAt: Date };
    defenseRequestedAt?: Date;
    defenseRequestedBy?: string;
  }): Protest {
    const id = ProtestId.create(props.id);
    const raceId = RaceId.create(props.raceId);
    const protestingDriverId = DriverId.create(props.protestingDriverId);
    const accusedDriverId = DriverId.create(props.accusedDriverId);
    const incident = ProtestIncident.create(props.incident.lap, props.incident.description, props.incident.timeInRace);
    const comment = props.comment ? ProtestComment.create(props.comment) : undefined;
    const proofVideoUrl = props.proofVideoUrl ? VideoUrl.create(props.proofVideoUrl) : undefined;
    const status = ProtestStatus.create(props.status || 'pending');
    const reviewedBy = props.reviewedBy ? StewardId.create(props.reviewedBy) : undefined;
    const decisionNotes = props.decisionNotes ? DecisionNotes.create(props.decisionNotes) : undefined;
    const filedAt = FiledAt.create(props.filedAt || new Date());
    const reviewedAt = props.reviewedAt ? ReviewedAt.create(props.reviewedAt) : undefined;
    const defense = props.defense ? ProtestDefense.create(props.defense.statement, props.defense.submittedAt, props.defense.videoUrl) : undefined;
    const defenseRequestedAt = props.defenseRequestedAt ? DefenseRequestedAt.create(props.defenseRequestedAt) : undefined;
    const defenseRequestedBy = props.defenseRequestedBy ? StewardId.create(props.defenseRequestedBy) : undefined;

    const protestProps: ProtestProps = {
      id,
      raceId,
      protestingDriverId,
      accusedDriverId,
      incident,
      status,
      filedAt,
    };
    
    if (comment !== undefined) protestProps.comment = comment;
    if (proofVideoUrl !== undefined) protestProps.proofVideoUrl = proofVideoUrl;
    if (reviewedBy !== undefined) protestProps.reviewedBy = reviewedBy;
    if (decisionNotes !== undefined) protestProps.decisionNotes = decisionNotes;
    if (reviewedAt !== undefined) protestProps.reviewedAt = reviewedAt;
    if (defense !== undefined) protestProps.defense = defense;
    if (defenseRequestedAt !== undefined) protestProps.defenseRequestedAt = defenseRequestedAt;
    if (defenseRequestedBy !== undefined) protestProps.defenseRequestedBy = defenseRequestedBy;
    
    return new Protest(protestProps);
  }

  static rehydrate(props: {
    id: string;
    raceId: string;
    protestingDriverId: string;
    accusedDriverId: string;
    incident: { lap: number; description: string; timeInRace?: number };
    comment?: string;
    proofVideoUrl?: string;
    status: string;
    reviewedBy?: string;
    decisionNotes?: string;
    filedAt: Date;
    reviewedAt?: Date;
    defense?: { statement: string; videoUrl?: string; submittedAt: Date };
    defenseRequestedAt?: Date;
    defenseRequestedBy?: string;
  }): Protest {
    const id = ProtestId.create(props.id);
    const raceId = RaceId.create(props.raceId);
    const protestingDriverId = DriverId.create(props.protestingDriverId);
    const accusedDriverId = DriverId.create(props.accusedDriverId);
    const incident = ProtestIncident.create(props.incident.lap, props.incident.description, props.incident.timeInRace);
    const comment = props.comment ? ProtestComment.create(props.comment) : undefined;
    const proofVideoUrl = props.proofVideoUrl ? VideoUrl.create(props.proofVideoUrl) : undefined;
    const status = ProtestStatus.create(props.status);
    const reviewedBy = props.reviewedBy ? StewardId.create(props.reviewedBy) : undefined;
    const decisionNotes = props.decisionNotes ? DecisionNotes.create(props.decisionNotes) : undefined;
    const filedAt = FiledAt.create(props.filedAt);
    const reviewedAt = props.reviewedAt ? ReviewedAt.create(props.reviewedAt) : undefined;
    const defense = props.defense ? ProtestDefense.create(props.defense.statement, props.defense.submittedAt, props.defense.videoUrl) : undefined;
    const defenseRequestedAt = props.defenseRequestedAt ? DefenseRequestedAt.create(props.defenseRequestedAt) : undefined;
    const defenseRequestedBy = props.defenseRequestedBy ? StewardId.create(props.defenseRequestedBy) : undefined;

    const protestProps: ProtestProps = {
      id,
      raceId,
      protestingDriverId,
      accusedDriverId,
      incident,
      status,
      filedAt,
    };

    if (comment !== undefined) protestProps.comment = comment;
    if (proofVideoUrl !== undefined) protestProps.proofVideoUrl = proofVideoUrl;
    if (reviewedBy !== undefined) protestProps.reviewedBy = reviewedBy;
    if (decisionNotes !== undefined) protestProps.decisionNotes = decisionNotes;
    if (reviewedAt !== undefined) protestProps.reviewedAt = reviewedAt;
    if (defense !== undefined) protestProps.defense = defense;
    if (defenseRequestedAt !== undefined) protestProps.defenseRequestedAt = defenseRequestedAt;
    if (defenseRequestedBy !== undefined) protestProps.defenseRequestedBy = defenseRequestedBy;

    return new Protest(protestProps);
  }

  get id(): string { return this.props.id.toString(); }
  get raceId(): string { return this.props.raceId.toString(); }
  get protestingDriverId(): string { return this.props.protestingDriverId.toString(); }
  get accusedDriverId(): string { return this.props.accusedDriverId.toString(); }
  get incident(): ProtestIncident { return this.props.incident; }
  get comment(): string | undefined { return this.props.comment?.toString(); }
  get proofVideoUrl(): string | undefined { return this.props.proofVideoUrl?.toString(); }
  get status(): ProtestStatus { return this.props.status; }
  get reviewedBy(): string | undefined { return this.props.reviewedBy?.toString(); }
  get decisionNotes(): string | undefined { return this.props.decisionNotes?.toString(); }
  get filedAt(): Date { return this.props.filedAt.toDate(); }
  get reviewedAt(): Date | undefined { return this.props.reviewedAt?.toDate(); }
  get defense(): ProtestDefense | undefined { return this.props.defense; }
  get defenseRequestedAt(): Date | undefined { return this.props.defenseRequestedAt?.toDate(); }
  get defenseRequestedBy(): string | undefined { return this.props.defenseRequestedBy?.toString(); }

  isPending(): boolean {
    return this.props.status.toString() === 'pending';
  }

  isAwaitingDefense(): boolean {
    return this.props.status.toString() === 'awaiting_defense';
  }

  isUnderReview(): boolean {
    return this.props.status.toString() === 'under_review';
  }

  isResolved(): boolean {
    return ['upheld', 'dismissed', 'withdrawn'].includes(this.props.status.toString());
  }

  hasDefense(): boolean {
    return this.props.defense !== undefined;
  }

  canRequestDefense(): boolean {
    return this.isPending() && !this.hasDefense() && !this.props.defenseRequestedAt;
  }

  canSubmitDefense(): boolean {
    return this.isAwaitingDefense() && !this.hasDefense();
  }

  /**
   * Request defense from the accused driver
   */
  requestDefense(stewardId: string): Protest {
    if (!this.canRequestDefense()) {
      throw new RacingDomainInvariantError('Defense can only be requested for pending protests without existing defense');
    }
    return new Protest({
      ...this.props,
      status: ProtestStatus.create('awaiting_defense'),
      defenseRequestedAt: DefenseRequestedAt.create(new Date()),
      defenseRequestedBy: StewardId.create(stewardId),
    });
  }

  /**
   * Submit defense from the accused driver
   */
  submitDefense(statement: string, videoUrl?: string): Protest {
    if (!this.canSubmitDefense()) {
      throw new RacingDomainInvariantError('Defense can only be submitted when protest is awaiting defense');
    }
    if (!statement?.trim()) {
      throw new RacingDomainValidationError('Defense statement is required');
    }
    const defense = ProtestDefense.create(statement.trim(), new Date(), videoUrl);

    return new Protest({
      ...this.props,
      status: ProtestStatus.create('under_review'),
      defense,
    });
  }

  /**
   * Start reviewing the protest (without requiring defense)
   */
  startReview(stewardId: string): Protest {
    if (!this.isPending() && !this.isAwaitingDefense()) {
      throw new RacingDomainInvariantError('Only pending or awaiting-defense protests can be put under review');
    }
    return new Protest({
      ...this.props,
      status: ProtestStatus.create('under_review'),
      reviewedBy: StewardId.create(stewardId),
    });
  }

  /**
   * Uphold the protest (finding the accused guilty)
   */
  uphold(stewardId: string, decisionNotes: string): Protest {
    if (!this.isPending() && !this.isUnderReview() && !this.isAwaitingDefense()) {
      throw new RacingDomainInvariantError('Only pending, awaiting-defense, or under-review protests can be upheld');
    }
    return new Protest({
      ...this.props,
      status: ProtestStatus.create('upheld'),
      reviewedBy: StewardId.create(stewardId),
      decisionNotes: DecisionNotes.create(decisionNotes),
      reviewedAt: ReviewedAt.create(new Date()),
    });
  }

  /**
   * Dismiss the protest (finding no fault)
   */
  dismiss(stewardId: string, decisionNotes: string): Protest {
    if (!this.isPending() && !this.isUnderReview() && !this.isAwaitingDefense()) {
      throw new RacingDomainInvariantError('Only pending, awaiting-defense, or under-review protests can be dismissed');
    }
    return new Protest({
      ...this.props,
      status: ProtestStatus.create('dismissed'),
      reviewedBy: StewardId.create(stewardId),
      decisionNotes: DecisionNotes.create(decisionNotes),
      reviewedAt: ReviewedAt.create(new Date()),
    });
  }

  /**
   * Withdraw the protest (by the protesting driver)
   */
  withdraw(): Protest {
    if (this.isResolved()) {
      throw new RacingDomainInvariantError('Cannot withdraw a resolved protest');
    }
    return new Protest({
      ...this.props,
      status: ProtestStatus.create('withdrawn'),
      reviewedAt: ReviewedAt.create(new Date()),
    });
  }
}