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

export interface TeamRatingEventSource {
  type: 'race' | 'penalty' | 'vote' | 'adminAction' | 'manualAdjustment';
  id?: string; // e.g., raceId, penaltyId, voteId
}

export interface TeamRatingEventReason {
  code: string;
  description?: string;
}

export interface TeamRatingEventVisibility {
  public: boolean;
}

export interface TeamRatingEventProps {
  id: TeamRatingEventId;
  teamId: string;
  dimension: TeamRatingDimensionKey;
  delta: TeamRatingDelta;
  weight?: number;
  occurredAt: Date;
  createdAt: Date;
  source: TeamRatingEventSource;
  reason: TeamRatingEventReason;
  visibility: TeamRatingEventVisibility;
  version: number;
}

export class TeamRatingEvent extends Entity<TeamRatingEventId> {
  readonly teamId: string;
  readonly dimension: TeamRatingDimensionKey;
  readonly delta: TeamRatingDelta;
  readonly weight: number | undefined;
  readonly occurredAt: Date;
  readonly createdAt: Date;
  readonly source: TeamRatingEventSource;
  readonly reason: TeamRatingEventReason;
  readonly visibility: TeamRatingEventVisibility;
  readonly version: number;

  private constructor(props: TeamRatingEventProps) {
    super(props.id);
    
    this.teamId = props.teamId;
    this.dimension = props.dimension;
    this.delta = props.delta;
    this.weight = props.weight;
    this.occurredAt = props.occurredAt;
    this.createdAt = props.createdAt;
    this.source = props.source;
    this.reason = props.reason;
    this.visibility = props.visibility;
    this.version = props.version;
  }

  /**
   * Factory method to create a new TeamRatingEvent.
   */
  static create(props: {
    id: TeamRatingEventId;
    teamId: string;
    dimension: TeamRatingDimensionKey;
    delta: TeamRatingDelta;
    weight?: number;
    occurredAt: Date;
    createdAt: Date;
    source: TeamRatingEventSource;
    reason: TeamRatingEventReason;
    visibility: TeamRatingEventVisibility;
    version: number;
  }): TeamRatingEvent {
    // Validate required fields
    if (!props.teamId || props.teamId.trim().length === 0) {
      throw new RacingDomainValidationError('Team ID is required');
    }

    if (!props.dimension) {
      throw new RacingDomainValidationError('Dimension is required');
    }

    if (!props.delta) {
      throw new RacingDomainValidationError('Delta is required');
    }

    if (!props.source) {
      throw new RacingDomainValidationError('Source is required');
    }

    if (!props.reason) {
      throw new RacingDomainValidationError('Reason is required');
    }

    if (!props.visibility) {
      throw new RacingDomainValidationError('Visibility is required');
    }

    if (props.weight !== undefined && (typeof props.weight !== 'number' || props.weight <= 0)) {
      throw new RacingDomainValidationError('Weight must be a positive number if provided');
    }

    const now = new Date();
    if (props.occurredAt > now) {
      throw new RacingDomainValidationError('Occurrence date cannot be in the future');
    }

    if (props.createdAt > now) {
      throw new RacingDomainValidationError('Creation date cannot be in the future');
    }

    if (props.version < 1) {
      throw new RacingDomainValidationError('Version must be at least 1');
    }

    // Validate invariants
    if (props.dimension.value === 'adminTrust' && props.source.type === 'race') {
      throw new RacingDomainInvariantError(
        'adminTrust dimension cannot be updated from race events'
      );
    }

    if (props.dimension.value === 'driving' && props.source.type === 'vote') {
      throw new RacingDomainInvariantError(
        'driving dimension cannot be updated from vote events'
      );
    }

    return new TeamRatingEvent(props);
  }

  /**
   * Rehydrate event from stored data (assumes data is already validated).
   */
  static rehydrate(props: {
    id: TeamRatingEventId;
    teamId: string;
    dimension: TeamRatingDimensionKey;
    delta: TeamRatingDelta;
    weight?: number;
    occurredAt: Date;
    createdAt: Date;
    source: TeamRatingEventSource;
    reason: TeamRatingEventReason;
    visibility: TeamRatingEventVisibility;
    version: number;
  }): TeamRatingEvent {
    // Rehydration assumes data is already validated (from persistence)
    return new TeamRatingEvent(props);
  }

  /**
   * Compare with another event.
   */
  equals(other: Entity<TeamRatingEventId>): boolean {
    return this.id.equals(other.id);
  }

  /**
   * Return plain object representation for serialization.
   */
  toJSON(): object {
    return {
      id: this.id.value,
      teamId: this.teamId,
      dimension: this.dimension.value,
      delta: this.delta.value,
      weight: this.weight,
      occurredAt: this.occurredAt.toISOString(),
      createdAt: this.createdAt.toISOString(),
      source: this.source,
      reason: this.reason,
      visibility: this.visibility,
      version: this.version,
    };
  }
}