import { TeamRatingEvent } from '../entities/TeamRatingEvent';
import { TeamRatingDelta } from '../value-objects/TeamRatingDelta';
import { TeamRatingDimensionKey } from '../value-objects/TeamRatingDimensionKey';
import { TeamRatingEventId } from '../value-objects/TeamRatingEventId';

export interface TeamRaceFactsDto {
  raceId: string;
  teamId: string;
  results: Array<{
    teamId: string;
    position: number;
    incidents: number;
    status: 'finished' | 'dnf' | 'dsq' | 'dns' | 'afk';
    fieldSize: number;
    strengthOfField: number; // Average rating of competing teams
  }>;
}

export interface TeamPenaltyInput {
  teamId: string;
  penaltyType: 'minor' | 'major' | 'critical';
  severity: 'low' | 'medium' | 'high';
  incidentCount?: number;
}

export interface TeamVoteInput {
  teamId: string;
  outcome: 'positive' | 'negative';
  voteCount: number;
  eligibleVoterCount: number;
  percentPositive: number;
}

export interface TeamAdminActionInput {
  teamId: string;
  actionType: 'bonus' | 'penalty' | 'warning';
  severity?: 'low' | 'medium' | 'high';
}

/**
 * Domain Service: TeamRatingEventFactory
 *
 * Factory for creating team rating events from various sources.
 * Mirrors the RatingEventFactory pattern for user ratings.
 *
 * Pure domain logic - no persistence concerns.
 */
export class TeamRatingEventFactory {
  /**
   * Create rating events from a team's race finish.
   * Generates driving dimension events.
   */
  static createFromRaceFinish(input: {
    teamId: string;
    position: number;
    incidents: number;
    status: 'finished' | 'dnf' | 'dsq' | 'dns' | 'afk';
    fieldSize: number;
    strengthOfField: number;
    raceId: string;
  }): TeamRatingEvent[] {
    const events: TeamRatingEvent[] = [];
    const now = new Date();

    if (input.status === 'finished') {
      // Performance delta based on position and field strength
      const performanceDelta = this.calculatePerformanceDelta(
        input.position,
        input.fieldSize,
        input.strengthOfField
      );

      if (performanceDelta !== 0) {
        events.push(
          TeamRatingEvent.create({
            id: TeamRatingEventId.generate(),
            teamId: input.teamId,
            dimension: TeamRatingDimensionKey.create('driving'),
            delta: TeamRatingDelta.create(performanceDelta),
            weight: 1,
            occurredAt: now,
            createdAt: now,
            source: { type: 'race', id: input.raceId },
            reason: {
              code: 'RACE_PERFORMANCE',
              description: `Finished ${input.position}${this.getOrdinalSuffix(input.position)} in race`,
            },
            visibility: { public: true },
            version: 1,
          })
        );
      }

      // Gain bonus for beating higher-rated teams
      const gainBonus = this.calculateGainBonus(input.position, input.strengthOfField);
      if (gainBonus !== 0) {
        events.push(
          TeamRatingEvent.create({
            id: TeamRatingEventId.generate(),
            teamId: input.teamId,
            dimension: TeamRatingDimensionKey.create('driving'),
            delta: TeamRatingDelta.create(gainBonus),
            weight: 0.5,
            occurredAt: now,
            createdAt: now,
            source: { type: 'race', id: input.raceId },
            reason: {
              code: 'RACE_GAIN_BONUS',
              description: `Bonus for beating higher-rated opponents`,
            },
            visibility: { public: true },
            version: 1,
          })
        );
      }
    }

    // Incident penalty
    if (input.incidents > 0) {
      const incidentPenalty = this.calculateIncidentPenalty(input.incidents);
      events.push(
        TeamRatingEvent.create({
          id: TeamRatingEventId.generate(),
          teamId: input.teamId,
          dimension: TeamRatingDimensionKey.create('driving'),
          delta: TeamRatingDelta.create(-incidentPenalty),
          weight: 1,
          occurredAt: now,
          createdAt: now,
          source: { type: 'race', id: input.raceId },
          reason: {
            code: 'RACE_INCIDENTS',
            description: `${input.incidents} incident${input.incidents > 1 ? 's' : ''}`,
          },
          visibility: { public: true },
          version: 1,
        })
      );
    }

    // Status-based penalties
    if (input.status === 'dnf') {
      events.push(
        TeamRatingEvent.create({
          id: TeamRatingEventId.generate(),
          teamId: input.teamId,
          dimension: TeamRatingDimensionKey.create('driving'),
          delta: TeamRatingDelta.create(-15),
          weight: 1,
          occurredAt: now,
          createdAt: now,
          source: { type: 'race', id: input.raceId },
          reason: {
            code: 'RACE_DNF',
            description: 'Did not finish',
          },
          visibility: { public: true },
          version: 1,
        })
      );
    } else if (input.status === 'dsq') {
      events.push(
        TeamRatingEvent.create({
          id: TeamRatingEventId.generate(),
          teamId: input.teamId,
          dimension: TeamRatingDimensionKey.create('driving'),
          delta: TeamRatingDelta.create(-25),
          weight: 1,
          occurredAt: now,
          createdAt: now,
          source: { type: 'race', id: input.raceId },
          reason: {
            code: 'RACE_DSQ',
            description: 'Disqualified',
          },
          visibility: { public: true },
          version: 1,
        })
      );
    } else if (input.status === 'dns') {
      events.push(
        TeamRatingEvent.create({
          id: TeamRatingEventId.generate(),
          teamId: input.teamId,
          dimension: TeamRatingDimensionKey.create('driving'),
          delta: TeamRatingDelta.create(-10),
          weight: 1,
          occurredAt: now,
          createdAt: now,
          source: { type: 'race', id: input.raceId },
          reason: {
            code: 'RACE_DNS',
            description: 'Did not start',
          },
          visibility: { public: true },
          version: 1,
        })
      );
    } else if (input.status === 'afk') {
      events.push(
        TeamRatingEvent.create({
          id: TeamRatingEventId.generate(),
          teamId: input.teamId,
          dimension: TeamRatingDimensionKey.create('driving'),
          delta: TeamRatingDelta.create(-20),
          weight: 1,
          occurredAt: now,
          createdAt: now,
          source: { type: 'race', id: input.raceId },
          reason: {
            code: 'RACE_AFK',
            description: 'Away from keyboard',
          },
          visibility: { public: true },
          version: 1,
        })
      );
    }

    return events;
  }

  /**
   * Create rating events from multiple race results.
   * Returns events grouped by team ID.
   */
  static createDrivingEventsFromRace(raceFacts: TeamRaceFactsDto): Map<string, TeamRatingEvent[]> {
    const eventsByTeam = new Map<string, TeamRatingEvent[]>();

    for (const result of raceFacts.results) {
      const events = this.createFromRaceFinish({
        teamId: result.teamId,
        position: result.position,
        incidents: result.incidents,
        status: result.status,
        fieldSize: raceFacts.results.length,
        strengthOfField: 50, // Default strength if not provided
        raceId: raceFacts.raceId,
      });

      if (events.length > 0) {
        eventsByTeam.set(result.teamId, events);
      }
    }

    return eventsByTeam;
  }

  /**
   * Create rating events from a penalty.
   * Generates both driving and adminTrust events.
   */
  static createFromPenalty(input: TeamPenaltyInput): TeamRatingEvent[] {
    const now = new Date();
    const events: TeamRatingEvent[] = [];

    // Driving dimension penalty
    const drivingDelta = this.calculatePenaltyDelta(input.penaltyType, input.severity, 'driving');
    if (drivingDelta !== 0) {
      events.push(
        TeamRatingEvent.create({
          id: TeamRatingEventId.generate(),
          teamId: input.teamId,
          dimension: TeamRatingDimensionKey.create('driving'),
          delta: TeamRatingDelta.create(drivingDelta),
          weight: 1,
          occurredAt: now,
          createdAt: now,
          source: { type: 'penalty', id: input.penaltyType },
          reason: {
            code: 'PENALTY_DRIVING',
            description: `${input.penaltyType} penalty for driving violations`,
          },
          visibility: { public: true },
          version: 1,
        })
      );
    }

    // AdminTrust dimension penalty
    const adminDelta = this.calculatePenaltyDelta(input.penaltyType, input.severity, 'adminTrust');
    if (adminDelta !== 0) {
      events.push(
        TeamRatingEvent.create({
          id: TeamRatingEventId.generate(),
          teamId: input.teamId,
          dimension: TeamRatingDimensionKey.create('adminTrust'),
          delta: TeamRatingDelta.create(adminDelta),
          weight: 1,
          occurredAt: now,
          createdAt: now,
          source: { type: 'penalty', id: input.penaltyType },
          reason: {
            code: 'PENALTY_ADMIN',
            description: `${input.penaltyType} penalty for rule violations`,
          },
          visibility: { public: true },
          version: 1,
        })
      );
    }

    return events;
  }

  /**
   * Create rating events from a vote outcome.
   * Generates adminTrust events.
   */
  static createFromVote(input: TeamVoteInput): TeamRatingEvent[] {
    const now = new Date();
    const events: TeamRatingEvent[] = [];

    // Calculate delta based on vote outcome
    const delta = this.calculateVoteDelta(
      input.outcome,
      input.eligibleVoterCount,
      input.voteCount,
      input.percentPositive
    );

    if (delta !== 0) {
      events.push(
        TeamRatingEvent.create({
          id: TeamRatingEventId.generate(),
          teamId: input.teamId,
          dimension: TeamRatingDimensionKey.create('adminTrust'),
          delta: TeamRatingDelta.create(delta),
          weight: input.voteCount, // Weight by number of votes
          occurredAt: now,
          createdAt: now,
          source: { type: 'vote', id: 'admin_vote' },
          reason: {
            code: input.outcome === 'positive' ? 'VOTE_POSITIVE' : 'VOTE_NEGATIVE',
            description: `Admin vote outcome: ${input.outcome}`,
          },
          visibility: { public: true },
          version: 1,
        })
      );
    }

    return events;
  }

  /**
   * Create rating events from an admin action.
   * Generates adminTrust events.
   */
  static createFromAdminAction(input: TeamAdminActionInput): TeamRatingEvent[] {
    const now = new Date();
    const events: TeamRatingEvent[] = [];

    if (input.actionType === 'bonus') {
      events.push(
        TeamRatingEvent.create({
          id: TeamRatingEventId.generate(),
          teamId: input.teamId,
          dimension: TeamRatingDimensionKey.create('adminTrust'),
          delta: TeamRatingDelta.create(5),
          weight: 1,
          occurredAt: now,
          createdAt: now,
          source: { type: 'adminAction', id: 'bonus' },
          reason: {
            code: 'ADMIN_BONUS',
            description: 'Admin bonus for positive contribution',
          },
          visibility: { public: true },
          version: 1,
        })
      );
    } else if (input.actionType === 'penalty') {
      const delta = input.severity === 'high' ? -15 : input.severity === 'medium' ? -10 : -5;
      events.push(
        TeamRatingEvent.create({
          id: TeamRatingEventId.generate(),
          teamId: input.teamId,
          dimension: TeamRatingDimensionKey.create('adminTrust'),
          delta: TeamRatingDelta.create(delta),
          weight: 1,
          occurredAt: now,
          createdAt: now,
          source: { type: 'adminAction', id: 'penalty' },
          reason: {
            code: 'ADMIN_PENALTY',
            description: `Admin penalty (${input.severity} severity)`,
          },
          visibility: { public: true },
          version: 1,
        })
      );
    } else if (input.actionType === 'warning') {
      events.push(
        TeamRatingEvent.create({
          id: TeamRatingEventId.generate(),
          teamId: input.teamId,
          dimension: TeamRatingDimensionKey.create('adminTrust'),
          delta: TeamRatingDelta.create(3),
          weight: 1,
          occurredAt: now,
          createdAt: now,
          source: { type: 'adminAction', id: 'warning' },
          reason: {
            code: 'ADMIN_WARNING_RESPONSE',
            description: 'Response to admin warning',
          },
          visibility: { public: true },
          version: 1,
        })
      );
    }

    return events;
  }

  // Private helper methods

  private static calculatePerformanceDelta(
    position: number,
    fieldSize: number,
    strengthOfField: number
  ): number {
    // Base delta from position (1st = +20, last = -20)
    const positionFactor = ((fieldSize - position + 1) / fieldSize) * 40 - 20;

    // Adjust for field strength
    const strengthFactor = (strengthOfField - 50) * 0.1; // +/- 5 for strong/weak fields

    return Math.round((positionFactor + strengthFactor) * 10) / 10;
  }

  private static calculateGainBonus(position: number, strengthOfField: number): number {
    // Bonus for beating teams with higher ratings
    if (strengthOfField > 60 && position <= Math.floor(strengthOfField / 10)) {
      return 5;
    }
    return 0;
  }

  private static calculateIncidentPenalty(incidents: number): number {
    // Exponential penalty for multiple incidents
    return Math.min(incidents * 2, 20);
  }

  private static calculatePenaltyDelta(
    penaltyType: 'minor' | 'major' | 'critical',
    severity: 'low' | 'medium' | 'high',
    dimension: 'driving' | 'adminTrust'
  ): number {
    const baseValues = {
      minor: { driving: -5, adminTrust: -3 },
      major: { driving: -10, adminTrust: -8 },
      critical: { driving: -20, adminTrust: -15 },
    };

    const severityMultipliers = {
      low: 1,
      medium: 1.5,
      high: 2,
    };

    const base = baseValues[penaltyType][dimension];
    const multiplier = severityMultipliers[severity];

    return Math.round(base * multiplier);
  }

  private static calculateVoteDelta(
    outcome: 'positive' | 'negative',
    eligibleVoterCount: number,
    voteCount: number,
    percentPositive: number
  ): number {
    if (voteCount === 0) return 0;

    const participationRate = voteCount / eligibleVoterCount;
    const strength = (percentPositive / 100) * 2 - 1; // -1 to +1

    // Base delta of +/- 10, scaled by participation and strength
    const baseDelta = outcome === 'positive' ? 10 : -10;
    const scaledDelta = baseDelta * participationRate * (0.5 + Math.abs(strength) * 0.5);

    return Math.round(scaledDelta * 10) / 10;
  }

  private static getOrdinalSuffix(position: number): string {
    const j = position % 10;
    const k = position % 100;

    if (j === 1 && k !== 11) return 'st';
    if (j === 2 && k !== 12) return 'nd';
    if (j === 3 && k !== 13) return 'rd';
    return 'th';
  }
}