/**
 * Domain Entity: League
 *
 * Represents a league in the GridPilot platform.
 * Immutable entity with factory methods and domain validation.
 */

import { MediaReference } from '@core/domain/media/MediaReference';
import { Entity } from '@core/shared/domain/Entity';
import { RacingDomainInvariantError, RacingDomainValidationError } from '../errors/RacingDomainError';
import { LeagueVisibility, LeagueVisibilityType } from '../value-objects/LeagueVisibility';
import { MaxParticipants } from '../value-objects/MaxParticipants';
import { ParticipantCount } from '../value-objects/ParticipantCount';
import { SessionDuration } from '../value-objects/SessionDuration';
import { LeagueCreatedAt } from './LeagueCreatedAt';
import { LeagueDescription } from './LeagueDescription';
import { LeagueId } from './LeagueId';
import { LeagueName } from './LeagueName';
import { LeagueOwnerId } from './LeagueOwnerId';
import { LeagueSocialLinks } from './LeagueSocialLinks';

/**
 * Stewarding decision mode for protests
 */
export type StewardingDecisionMode =
  | 'admin_only'           // Only admins can decide
  | 'steward_decides'      // Single steward makes decision
  | 'steward_vote'         // X stewards must vote to uphold
  | 'member_vote'          // X members must vote to uphold
  | 'steward_veto'         // Upheld unless X stewards vote against
  | 'member_veto';         // Upheld unless X members vote against

export interface StewardingSettings {
  /**
    * How protest decisions are made
    */
  decisionMode: StewardingDecisionMode;
  /**
    * Number of votes required to uphold/reject a protest
    * Used with steward_vote, member_vote, steward_veto, member_veto modes
    */
  requiredVotes?: number;
  /**
    * Whether to require a defense from the accused before deciding
    */
  requireDefense?: boolean;
  /**
    * Time limit (hours) for accused to submit defense
    */
  defenseTimeLimit?: number;
  /**
    * Time limit (hours) for voting to complete
    */
  voteTimeLimit?: number;
  /**
    * Time limit (hours) after race ends when protests can be filed
    */
  protestDeadlineHours?: number;
  /**
    * Time limit (hours) after race ends when stewarding is closed (no more decisions)
    */
  stewardingClosesHours?: number;
  /**
    * Whether to notify the accused when a protest is filed
    */
  notifyAccusedOnProtest?: boolean;
  /**
    * Whether to notify eligible voters when a vote is required
    */
  notifyOnVoteRequired?: boolean;
}

export interface LeagueSettings {
  pointsSystem: 'f1-2024' | 'indycar' | 'custom';
  sessionDuration?: number;
  qualifyingFormat?: 'single-lap' | 'open';
  customPoints?: Record<number, number>;
  /**
    * Maximum number of drivers allowed in the league.
    * Used for simple capacity display on the website.
    */
  maxDrivers?: number;
  /**
    * Stewarding settings for protest handling
    */
  stewarding?: StewardingSettings;
  /**
    * League visibility type (ranked/unranked)
    * Determines participant requirements and rating impact
    */
  visibility?: LeagueVisibilityType;
}

export class League extends Entity<LeagueId> {
  readonly name: LeagueName;
  readonly description: LeagueDescription;
  readonly ownerId: LeagueOwnerId;
  readonly settings: LeagueSettings;
  readonly category?: string | undefined;
  readonly createdAt: LeagueCreatedAt;
  readonly socialLinks: LeagueSocialLinks | undefined;
  readonly logoRef: MediaReference;
  
  // Domain state for business rule enforcement
  private readonly _participantCount: ParticipantCount;
  private readonly _visibility: LeagueVisibility;

  private constructor(props: {
    id: LeagueId;
    name: LeagueName;
    description: LeagueDescription;
    ownerId: LeagueOwnerId;
    settings: LeagueSettings;
    category?: string | undefined;
    createdAt: LeagueCreatedAt;
    socialLinks?: LeagueSocialLinks;
    participantCount: ParticipantCount;
    visibility: LeagueVisibility;
    logoRef: MediaReference;
  }) {
    super(props.id);
    
    this.name = props.name;
    this.description = props.description;
    this.ownerId = props.ownerId;
    this.settings = props.settings;
    this.category = props.category;
    this.createdAt = props.createdAt;
    this.socialLinks = props.socialLinks;
    this._participantCount = props.participantCount;
    this._visibility = props.visibility;
    this.logoRef = props.logoRef;
  }

  /**
   * Factory method to create a new League entity
   * Enforces all business rules and invariants
   */
  static create(props: {
    id: string;
    name: string;
    description: string;
    ownerId: string;
    settings?: Partial<LeagueSettings>;
    category?: string | undefined;
    createdAt?: Date;
    socialLinks?: {
      discordUrl?: string;
      youtubeUrl?: string;
      websiteUrl?: string;
    };
    participantCount?: number;
    logoRef?: MediaReference;
  }): League {
    // Validate required fields
    if (!props.id || props.id.trim().length === 0) {
      throw new RacingDomainValidationError('League ID is required');
    }

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

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

    if (!props.ownerId || props.ownerId.trim().length === 0) {
      throw new RacingDomainValidationError('League owner ID is required');
    }

    const id = LeagueId.create(props.id);
    const name = LeagueName.create(props.name);
    const description = LeagueDescription.create(props.description);
    const ownerId = LeagueOwnerId.create(props.ownerId);
    const createdAt = LeagueCreatedAt.create(props.createdAt ?? new Date());

    // Determine visibility from settings or default to ranked
    const visibilityType = props.settings?.visibility ?? 'ranked';
    const visibility = LeagueVisibility.fromString(visibilityType);

    // Validate maxDrivers against visibility constraints
    const maxDrivers = props.settings?.maxDrivers ?? 32;
    const maxParticipants = MaxParticipants.create(maxDrivers);
    const maxValidation = visibility.validateMaxParticipants(maxParticipants.toNumber());
    if (!maxValidation.valid) {
      throw new RacingDomainValidationError(maxValidation.error!);
    }

    // Validate session duration if provided
    if (props.settings?.sessionDuration !== undefined) {
      try {
        SessionDuration.create(props.settings.sessionDuration);
      } catch (error) {
        if (error instanceof RacingDomainValidationError) {
          throw error;
        }
        throw new RacingDomainValidationError('Invalid session duration');
      }
    }

    // Validate stewarding settings if provided
    if (props.settings?.stewarding) {
      League.validateStewardingSettings(props.settings.stewarding);
    }

    // Create default stewarding settings
    const defaultStewardingSettings: StewardingSettings = {
      decisionMode: 'admin_only',
      requireDefense: false,
      defenseTimeLimit: 48,
      voteTimeLimit: 72,
      protestDeadlineHours: 48,
      stewardingClosesHours: 168, // 7 days
      notifyAccusedOnProtest: true,
      notifyOnVoteRequired: true,
    };

    // Build final settings with validation
    const defaultSettings: LeagueSettings = {
      pointsSystem: 'f1-2024',
      sessionDuration: 60,
      qualifyingFormat: 'open',
      maxDrivers: maxDrivers,
      visibility: visibilityType,
      stewarding: defaultStewardingSettings,
    };

    const finalSettings = { ...defaultSettings, ...props.settings };

    // Validate participant count against visibility and max
    const participantCount = ParticipantCount.create(props.participantCount ?? 0);
    
    // Only validate minimum requirements if there are actual participants
    // This allows leagues to be created empty and populated later
    if (participantCount.toNumber() > 0) {
      const participantValidation = visibility.validateDriverCount(participantCount.toNumber());
      if (!participantValidation.valid) {
        throw new RacingDomainValidationError(participantValidation.error!);
      }
    }

    if (!maxParticipants.canAccommodate(participantCount.toNumber())) {
      throw new RacingDomainValidationError(
        `Participant count (${participantCount.toNumber()}) exceeds league capacity (${maxParticipants.toNumber()})`
      );
    }

    const socialLinks = props.socialLinks ? LeagueSocialLinks.create(props.socialLinks) : undefined;

    return new League({
      id,
      name,
      description,
      ownerId,
      settings: finalSettings,
      category: props.category,
      createdAt,
      ...(socialLinks !== undefined ? { socialLinks } : {}),
      participantCount,
      visibility,
      logoRef: props.logoRef ?? MediaReference.createSystemDefault('logo'),
    });
  }

  static rehydrate(props: {
    id: string;
    name: string;
    description: string;
    ownerId: string;
    settings: LeagueSettings;
    category?: string | undefined;
    createdAt: Date;
    participantCount: number;
    socialLinks?: {
      discordUrl?: string;
      youtubeUrl?: string;
      websiteUrl?: string;
    };
    logoRef?: MediaReference;
  }): League {
    const id = LeagueId.create(props.id);
    const name = LeagueName.create(props.name);
    const description = LeagueDescription.create(props.description);
    const ownerId = LeagueOwnerId.create(props.ownerId);
    const createdAt = LeagueCreatedAt.create(props.createdAt);

    const visibilityType = props.settings.visibility ?? 'ranked';
    const visibility = LeagueVisibility.fromString(visibilityType);

    const participantCount = ParticipantCount.create(props.participantCount);
    const socialLinks = props.socialLinks
      ? LeagueSocialLinks.create(props.socialLinks)
      : undefined;

    return new League({
      id,
      name,
      description,
      ownerId,
      settings: props.settings,
      category: props.category,
      createdAt,
      ...(socialLinks !== undefined ? { socialLinks } : {}),
      participantCount,
      visibility,
      logoRef: props.logoRef ?? MediaReference.createSystemDefault('logo'),
    });
  }
 
  /**
   * Validate stewarding settings configuration
   */
  private static validateStewardingSettings(settings: StewardingSettings): void {
    if (!settings.decisionMode) {
      throw new RacingDomainValidationError('Stewarding decision mode is required');
    }

    const votingModes = ['steward_vote', 'member_vote', 'steward_veto', 'member_veto'];
    if (votingModes.includes(settings.decisionMode)) {
      if (!settings.requiredVotes || settings.requiredVotes <= 0) {
        throw new RacingDomainValidationError(
          'Stewarding settings with voting modes require a positive requiredVotes value'
        );
      }
    }

    if (settings.requiredVotes !== undefined && !votingModes.includes(settings.decisionMode)) {
      throw new RacingDomainValidationError(
        'requiredVotes should only be provided for voting/veto modes'
      );
    }

    // Validate time limits
    if (settings.defenseTimeLimit !== undefined && settings.defenseTimeLimit < 0) {
      throw new RacingDomainValidationError('Defense time limit must be non-negative');
    }

    if (settings.voteTimeLimit !== undefined && settings.voteTimeLimit <= 0) {
      throw new RacingDomainValidationError('Vote time limit must be positive');
    }

    if (settings.protestDeadlineHours !== undefined && settings.protestDeadlineHours <= 0) {
      throw new RacingDomainValidationError('Protest deadline must be positive');
    }

    if (settings.stewardingClosesHours !== undefined && settings.stewardingClosesHours <= 0) {
      throw new RacingDomainValidationError('Stewarding close time must be positive');
    }
  }

  /**
   * Create a copy with updated properties
   * Validates all business rules on update
   */
  update(props: Partial<{
    name: string;
    description: string;
    ownerId: string;
    settings: LeagueSettings;
    category?: string | undefined;
    socialLinks?: {
      discordUrl?: string;
      youtubeUrl?: string;
      websiteUrl?: string;
    };
    logoRef: MediaReference;
  }>): League {
    const name = props.name ? LeagueName.create(props.name) : this.name;
    const description = props.description ? LeagueDescription.create(props.description) : this.description;
    const ownerId = props.ownerId ? LeagueOwnerId.create(props.ownerId) : this.ownerId;
    const socialLinks = props.socialLinks ? LeagueSocialLinks.create(props.socialLinks) : this.socialLinks;
    const logoRef = 'logoRef' in props ? props.logoRef! : this.logoRef;

    // If settings are being updated, validate them
    let newSettings = props.settings ?? this.settings;
    if (props.settings) {
      // Validate visibility constraints if visibility is changing
      if (props.settings.visibility && props.settings.visibility !== this.settings.visibility) {
        const newVisibility = LeagueVisibility.fromString(props.settings.visibility);
        const maxDrivers = props.settings.maxDrivers ?? this.settings.maxDrivers ?? 32;
        
        const maxValidation = newVisibility.validateMaxParticipants(maxDrivers);
        if (!maxValidation.valid) {
          throw new RacingDomainValidationError(maxValidation.error!);
        }

        const participantValidation = newVisibility.validateDriverCount(this._participantCount.toNumber());
        if (!participantValidation.valid) {
          throw new RacingDomainValidationError(participantValidation.error!);
        }
      }

      // Validate session duration if changing
      if (props.settings.sessionDuration !== undefined && props.settings.sessionDuration !== this.settings.sessionDuration) {
        try {
          SessionDuration.create(props.settings.sessionDuration);
        } catch (error) {
          if (error instanceof RacingDomainValidationError) {
            throw error;
          }
          throw new RacingDomainValidationError('Invalid session duration');
        }
      }

      // Validate max drivers if changing
      if (props.settings.maxDrivers !== undefined && props.settings.maxDrivers !== this.settings.maxDrivers) {
        const visibility = LeagueVisibility.fromString(this.settings.visibility ?? 'ranked');
        const maxValidation = visibility.validateMaxParticipants(props.settings.maxDrivers);
        if (!maxValidation.valid) {
          throw new RacingDomainValidationError(maxValidation.error!);
        }

        if (props.settings.maxDrivers < this._participantCount.toNumber()) {
          throw new RacingDomainValidationError(
            `Cannot reduce max drivers (${props.settings.maxDrivers}) below current participant count (${this._participantCount.toNumber()})`
          );
        }
      }

      // Validate stewarding settings if changing
      if (props.settings.stewarding) {
        League.validateStewardingSettings(props.settings.stewarding);
      }

      newSettings = { ...this.settings, ...props.settings };
    }

    return new League({
      id: this.id,
      name,
      description,
      ownerId,
      settings: newSettings,
      category: props.category ?? this.category,
      createdAt: this.createdAt,
      ...(socialLinks !== undefined ? { socialLinks } : {}),
      participantCount: this._participantCount,
      visibility: this._visibility,
      logoRef: logoRef,
    });
  }

  /**
   * Add a participant to the league
   * Validates against capacity and visibility constraints
   */
  addParticipant(): League {
    const newCount = this._participantCount.increment();
    const maxDrivers = this.settings.maxDrivers ?? 32;
    
    if (newCount.toNumber() > maxDrivers) {
      throw new RacingDomainInvariantError(
        `Cannot add participant: league capacity (${maxDrivers}) would be exceeded`
      );
    }

    const visibility = LeagueVisibility.fromString(this.settings.visibility ?? 'ranked');
    const validation = visibility.validateDriverCount(newCount.toNumber());
    if (!validation.valid) {
      throw new RacingDomainInvariantError(validation.error!);
    }

    return new League({
      id: this.id,
      name: this.name,
      description: this.description,
      ownerId: this.ownerId,
      settings: this.settings,
      category: this.category,
      createdAt: this.createdAt,
      ...(this.socialLinks !== undefined ? { socialLinks: this.socialLinks } : {}),
      participantCount: newCount,
      visibility: this._visibility,
      logoRef: this.logoRef,
    });
  }

  /**
   * Remove a participant from the league
   */
  removeParticipant(): League {
    if (this._participantCount.isZero()) {
      throw new RacingDomainInvariantError('Cannot remove participant: league has no participants');
    }

    const newCount = this._participantCount.decrement();

    return new League({
      id: this.id,
      name: this.name,
      description: this.description,
      ownerId: this.ownerId,
      settings: this.settings,
      category: this.category,
      createdAt: this.createdAt,
      ...(this.socialLinks !== undefined ? { socialLinks: this.socialLinks } : {}),
      participantCount: newCount,
      visibility: this._visibility,
      logoRef: this.logoRef,
    });
  }

  /**
   * Get current participant count
   */
  getParticipantCount(): number {
    return this._participantCount.toNumber();
  }

  /**
   * Get league visibility
   */
  getVisibility(): LeagueVisibility {
    return this._visibility;
  }

  /**
   * Check if league is full
   */
  isFull(): boolean {
    const maxDrivers = this.settings.maxDrivers ?? 32;
    return this._participantCount.toNumber() >= maxDrivers;
  }

  /**
   * Check if league meets minimum participant requirements
   */
  meetsMinimumRequirements(): boolean {
    return this._visibility.meetsMinimumForVisibility(this._participantCount.toNumber());
  }

  /**
   * Check if league can accept more participants
   */
  canAcceptMore(): boolean {
    return !this.isFull();
  }

  equals(other: Entity<LeagueId>): boolean {
    if (!(other instanceof League)) {
      return false;
    }
    return this.id.equals(other.id);
  }
}