import { RatingEvent } from '../entities/RatingEvent';
import { RatingEventId } from '../value-objects/RatingEventId';
import { RatingDimensionKey } from '../value-objects/RatingDimensionKey';
import { RatingDelta } from '../value-objects/RatingDelta';
import { DrivingReasonCode } from '../value-objects/DrivingReasonCode';
import { AdminTrustReasonCode } from '../value-objects/AdminTrustReasonCode';

// Existing interfaces
interface RaceFinishInput {
  userId: string;
  raceId: string;
  position: number;
  totalDrivers: number;
  startPosition: number;
  incidents: number;
  fieldStrength: number;
  status: 'finished' | 'dnf' | 'dns' | 'dsq' | 'afk';
}

interface PenaltyInput {
  userId: string;
  penaltyId: string;
  penaltyType: 'incident' | 'admin_violation';
  severity: 'minor' | 'major';
  reason: string;
}

interface VoteInput {
  userId: string;
  voteSessionId: string;
  outcome: 'positive' | 'negative';
  voteCount: number;
  eligibleVoterCount: number;
  percentPositive: number;
}

interface AdminActionInput {
  userId: string;
  adminActionId: string;
  actionType: 'sla_response' | 'abuse_report' | 'rule_clarity';
  details: Record<string, unknown>;
}

// NEW: Enhanced interface for race facts (per plans section 5.1.2)
export interface RaceFactsDto {
  raceId: string;
  results: Array<{
    userId: string;
    startPos: number;
    finishPos: number;
    incidents: number;
    status: 'finished' | 'dnf' | 'dns' | 'dsq' | 'afk';
    sof?: number; // Optional strength of field
  }>;
}

/**
 * Domain Service: RatingEventFactory
 *
 * Pure, stateless factory that turns domain facts into rating events.
 * Follows the pattern of creating immutable entities from business facts.
 * Enhanced to support full driving event taxonomy from plans.
 */
export class RatingEventFactory {
  /**
   * Create rating events from race finish data
   * Handles performance, clean driving, and reliability dimensions
   */
  static createFromRaceFinish(input: RaceFinishInput): RatingEvent[] {
    const events: RatingEvent[] = [];
    const now = new Date();

    // Performance events (only for finished races)
    if (input.status === 'finished') {
      const performanceDelta = this.calculatePerformanceDelta(
        input.position,
        input.totalDrivers,
        input.startPosition,
        input.fieldStrength
      );

      if (performanceDelta !== 0) {
        events.push(
          RatingEvent.create({
            id: RatingEventId.generate(),
            userId: input.userId,
            dimension: RatingDimensionKey.create('driving'),
            delta: RatingDelta.create(performanceDelta),
            weight: 1,
            occurredAt: now,
            createdAt: now,
            source: { type: 'race', id: input.raceId },
            reason: {
              code: 'DRIVING_FINISH_STRENGTH_GAIN',
              summary: `Finished ${input.position}${this.getOrdinalSuffix(input.position)} in field of ${input.totalDrivers}`,
              details: {
                position: input.position,
                totalDrivers: input.totalDrivers,
                startPosition: input.startPosition,
                fieldStrength: input.fieldStrength,
                positionsGained: input.startPosition - input.position,
              },
            },
            visibility: { public: true, redactedFields: [] },
            version: 1,
          })
        );
      }

      // Positions gained bonus
      const positionsGained = input.startPosition - input.position;
      if (positionsGained > 0) {
        const gainBonus = Math.min(positionsGained * 2, 10); // Max 10 points
        events.push(
          RatingEvent.create({
            id: RatingEventId.generate(),
            userId: input.userId,
            dimension: RatingDimensionKey.create('driving'),
            delta: RatingDelta.create(gainBonus),
            weight: 0.5,
            occurredAt: now,
            createdAt: now,
            source: { type: 'race', id: input.raceId },
            reason: {
              code: 'DRIVING_POSITIONS_GAINED_BONUS',
              summary: `Gained ${positionsGained} positions`,
              details: { positionsGained },
            },
            visibility: { public: true, redactedFields: [] },
            version: 1,
          })
        );
      }
    }

    // Clean driving penalty (incidents)
    if (input.incidents > 0) {
      const incidentPenalty = Math.min(input.incidents * 5, 30); // Max 30 points penalty
      events.push(
        RatingEvent.create({
          id: RatingEventId.generate(),
          userId: input.userId,
          dimension: RatingDimensionKey.create('driving'),
          delta: RatingDelta.create(-incidentPenalty),
          weight: 1,
          occurredAt: now,
          createdAt: now,
          source: { type: 'race', id: input.raceId },
          reason: {
            code: 'DRIVING_INCIDENTS_PENALTY',
            summary: `${input.incidents} incident${input.incidents > 1 ? 's' : ''}`,
            details: { incidents: input.incidents },
          },
          visibility: { public: true, redactedFields: [] },
          version: 1,
        })
      );
    }

    // Reliability penalties
    if (input.status === 'dns') {
      events.push(
        RatingEvent.create({
          id: RatingEventId.generate(),
          userId: input.userId,
          dimension: RatingDimensionKey.create('driving'),
          delta: RatingDelta.create(-15),
          weight: 1,
          occurredAt: now,
          createdAt: now,
          source: { type: 'race', id: input.raceId },
          reason: {
            code: 'DRIVING_DNS_PENALTY',
            summary: 'Did not start',
            details: {},
          },
          visibility: { public: true, redactedFields: [] },
          version: 1,
        })
      );
    } else if (input.status === 'dnf') {
      events.push(
        RatingEvent.create({
          id: RatingEventId.generate(),
          userId: input.userId,
          dimension: RatingDimensionKey.create('driving'),
          delta: RatingDelta.create(-10),
          weight: 1,
          occurredAt: now,
          createdAt: now,
          source: { type: 'race', id: input.raceId },
          reason: {
            code: 'DRIVING_DNF_PENALTY',
            summary: 'Did not finish',
            details: {},
          },
          visibility: { public: true, redactedFields: [] },
          version: 1,
        })
      );
    } else if (input.status === 'dsq') {
      events.push(
        RatingEvent.create({
          id: RatingEventId.generate(),
          userId: input.userId,
          dimension: RatingDimensionKey.create('driving'),
          delta: RatingDelta.create(-25),
          weight: 1,
          occurredAt: now,
          createdAt: now,
          source: { type: 'race', id: input.raceId },
          reason: {
            code: 'DRIVING_DSQ_PENALTY',
            summary: 'Disqualified',
            details: {},
          },
          visibility: { public: true, redactedFields: [] },
          version: 1,
        })
      );
    } else if (input.status === 'afk') {
      events.push(
        RatingEvent.create({
          id: RatingEventId.generate(),
          userId: input.userId,
          dimension: RatingDimensionKey.create('driving'),
          delta: RatingDelta.create(-20),
          weight: 1,
          occurredAt: now,
          createdAt: now,
          source: { type: 'race', id: input.raceId },
          reason: {
            code: 'DRIVING_AFK_PENALTY',
            summary: 'AFK / Not responsive',
            details: {},
          },
          visibility: { public: true, redactedFields: [] },
          version: 1,
        })
      );
    }

    return events;
  }

  /**
   * NEW: Create rating events from race facts DTO
   * Supports multiple drivers and full event taxonomy
   * Returns a map of userId to events for efficient processing
   */
  static createDrivingEventsFromRace(raceFacts: RaceFactsDto): Map<string, RatingEvent[]> {
    const eventsByUser = new Map<string, RatingEvent[]>();
    const now = new Date();

    // Calculate field strength if not provided in all results
    const hasSof = raceFacts.results.some(r => r.sof !== undefined);
    let fieldStrength = 0;
    
    if (hasSof) {
      const sofResults = raceFacts.results.filter(r => r.sof !== undefined);
      fieldStrength = sofResults.reduce((sum, r) => sum + r.sof!, 0) / sofResults.length;
    } else {
      // Use average of finished positions as proxy
      const finishedResults = raceFacts.results.filter(r => r.status === 'finished');
      if (finishedResults.length > 0) {
        fieldStrength = finishedResults.reduce((sum, r) => sum + (r.finishPos * 100), 0) / finishedResults.length;
      }
    }

    for (const result of raceFacts.results) {
      const events: RatingEvent[] = [];

      // 1. Performance events (only for finished races)
      if (result.status === 'finished') {
        const performanceDelta = this.calculatePerformanceDelta(
          result.finishPos,
          raceFacts.results.length,
          result.startPos,
          fieldStrength
        );

        if (performanceDelta !== 0) {
          events.push(
            RatingEvent.create({
              id: RatingEventId.generate(),
              userId: result.userId,
              dimension: RatingDimensionKey.create('driving'),
              delta: RatingDelta.create(performanceDelta),
              weight: 1,
              occurredAt: now,
              createdAt: now,
              source: { type: 'race', id: raceFacts.raceId },
              reason: {
                code: DrivingReasonCode.create('DRIVING_FINISH_STRENGTH_GAIN').value,
                summary: `Finished ${result.finishPos}${this.getOrdinalSuffix(result.finishPos)} in field of ${raceFacts.results.length}`,
                details: {
                  startPos: result.startPos,
                  finishPos: result.finishPos,
                  positionsGained: result.startPos - result.finishPos,
                  fieldStrength: fieldStrength,
                  totalDrivers: raceFacts.results.length,
                },
              },
              visibility: { public: true, redactedFields: [] },
              version: 1,
            })
          );
        }

        // Positions gained bonus
        const positionsGained = result.startPos - result.finishPos;
        if (positionsGained > 0) {
          const gainBonus = Math.min(positionsGained * 2, 10);
          events.push(
            RatingEvent.create({
              id: RatingEventId.generate(),
              userId: result.userId,
              dimension: RatingDimensionKey.create('driving'),
              delta: RatingDelta.create(gainBonus),
              weight: 0.5,
              occurredAt: now,
              createdAt: now,
              source: { type: 'race', id: raceFacts.raceId },
              reason: {
                code: DrivingReasonCode.create('DRIVING_POSITIONS_GAINED_BONUS').value,
                summary: `Gained ${positionsGained} positions`,
                details: { positionsGained },
              },
              visibility: { public: true, redactedFields: [] },
              version: 1,
            })
          );
        }
      }

      // 2. Clean driving penalty (incidents)
      if (result.incidents > 0) {
        const incidentPenalty = Math.min(result.incidents * 5, 30);
        events.push(
          RatingEvent.create({
            id: RatingEventId.generate(),
            userId: result.userId,
            dimension: RatingDimensionKey.create('driving'),
            delta: RatingDelta.create(-incidentPenalty),
            weight: 1,
            occurredAt: now,
            createdAt: now,
            source: { type: 'race', id: raceFacts.raceId },
            reason: {
              code: DrivingReasonCode.create('DRIVING_INCIDENTS_PENALTY').value,
              summary: `${result.incidents} incident${result.incidents > 1 ? 's' : ''}`,
              details: { incidents: result.incidents },
            },
            visibility: { public: true, redactedFields: [] },
            version: 1,
          })
        );
      }

      // 3. Reliability penalties
      if (result.status !== 'finished') {
        let penaltyDelta = 0;
        let reasonCode: DrivingReasonCodeValue;

        switch (result.status) {
          case 'dns':
            penaltyDelta = -15;
            reasonCode = 'DRIVING_DNS_PENALTY';
            break;
          case 'dnf':
            penaltyDelta = -10;
            reasonCode = 'DRIVING_DNF_PENALTY';
            break;
          case 'dsq':
            penaltyDelta = -25;
            reasonCode = 'DRIVING_DSQ_PENALTY';
            break;
          case 'afk':
            penaltyDelta = -20;
            reasonCode = 'DRIVING_AFK_PENALTY';
            break;
          default:
            continue; // Skip unknown statuses
        }

        events.push(
          RatingEvent.create({
            id: RatingEventId.generate(),
            userId: result.userId,
            dimension: RatingDimensionKey.create('driving'),
            delta: RatingDelta.create(penaltyDelta),
            weight: 1,
            occurredAt: now,
            createdAt: now,
            source: { type: 'race', id: raceFacts.raceId },
            reason: {
              code: DrivingReasonCode.create(reasonCode).value,
              summary: this.getStatusSummary(result.status),
              details: { status: result.status },
            },
            visibility: { public: true, redactedFields: [] },
            version: 1,
          })
        );
      }

      if (events.length > 0) {
        eventsByUser.set(result.userId, events);
      }
    }

    return eventsByUser;
  }

  /**
   * Create rating events from penalty data
   */
  static createFromPenalty(input: PenaltyInput): RatingEvent[] {
    const now = new Date();
    const events: RatingEvent[] = [];

    if (input.penaltyType === 'incident') {
      const delta = input.severity === 'major' ? -15 : -5;
      events.push(
        RatingEvent.create({
          id: RatingEventId.generate(),
          userId: input.userId,
          dimension: RatingDimensionKey.create('driving'),
          delta: RatingDelta.create(delta),
          weight: 1,
          occurredAt: now,
          createdAt: now,
          source: { type: 'penalty', id: input.penaltyId },
          reason: {
            code: DrivingReasonCode.create('DRIVING_PENALTY_INVOLVEMENT_PENALTY').value,
            summary: input.reason,
            details: { severity: input.severity, type: input.penaltyType },
          },
          visibility: { public: true, redactedFields: [] },
          version: 1,
        })
      );
    } else if (input.penaltyType === 'admin_violation') {
      const delta = input.severity === 'major' ? -20 : -10;
      events.push(
        RatingEvent.create({
          id: RatingEventId.generate(),
          userId: input.userId,
          dimension: RatingDimensionKey.create('adminTrust'),
          delta: RatingDelta.create(delta),
          weight: 1,
          occurredAt: now,
          createdAt: now,
          source: { type: 'penalty', id: input.penaltyId },
          reason: {
            code: 'ADMIN_ACTION_ABUSE_REPORT_PENALTY',
            summary: input.reason,
            details: { severity: input.severity, type: input.penaltyType },
          },
          visibility: { public: false, redactedFields: ['reason.summary', 'reason.details'] },
          version: 1,
        })
      );
    }

    return events;
  }

  /**
   * Create rating events from vote outcome
   */
  static createFromVote(input: VoteInput): RatingEvent[] {
    const now = new Date();
    const events: RatingEvent[] = [];

    // Calculate delta based on vote outcome
    // Scale: -20 to +20 based on percentage
    let delta: number;
    if (input.outcome === 'positive') {
      delta = Math.round((input.percentPositive / 100) * 20); // 0 to +20
    } else {
      delta = -Math.round(((100 - input.percentPositive) / 100) * 20); // -20 to 0
    }

    if (delta !== 0) {
      const reasonCode = input.outcome === 'positive'
        ? AdminTrustReasonCode.create('ADMIN_VOTE_OUTCOME_POSITIVE').value
        : AdminTrustReasonCode.create('ADMIN_VOTE_OUTCOME_NEGATIVE').value;

      events.push(
        RatingEvent.create({
          id: RatingEventId.generate(),
          userId: input.userId,
          dimension: RatingDimensionKey.create('adminTrust'),
          delta: RatingDelta.create(delta),
          weight: input.voteCount, // Weight by number of votes
          occurredAt: now,
          createdAt: now,
          source: { type: 'vote', id: input.voteSessionId },
          reason: {
            code: reasonCode,
            summary: `Vote outcome: ${input.percentPositive}% positive (${input.voteCount}/${input.eligibleVoterCount})`,
            details: {
              voteCount: input.voteCount,
              eligibleVoterCount: input.eligibleVoterCount,
              percentPositive: input.percentPositive,
            },
          },
          visibility: { public: true, redactedFields: [] },
          version: 1,
        })
      );
    }

    return events;
  }

  /**
   * Create rating events from admin action
   */
  static createFromAdminAction(input: AdminActionInput): RatingEvent[] {
    const now = new Date();
    const events: RatingEvent[] = [];

    if (input.actionType === 'sla_response') {
      events.push(
        RatingEvent.create({
          id: RatingEventId.generate(),
          userId: input.userId,
          dimension: RatingDimensionKey.create('adminTrust'),
          delta: RatingDelta.create(5),
          weight: 1,
          occurredAt: now,
          createdAt: now,
          source: { type: 'adminAction', id: input.adminActionId },
          reason: {
            code: AdminTrustReasonCode.create('ADMIN_ACTION_SLA_BONUS').value,
            summary: 'Timely response to admin task',
            details: input.details,
          },
          visibility: { public: true, redactedFields: [] },
          version: 1,
        })
      );
    } else if (input.actionType === 'abuse_report') {
      events.push(
        RatingEvent.create({
          id: RatingEventId.generate(),
          userId: input.userId,
          dimension: RatingDimensionKey.create('adminTrust'),
          delta: RatingDelta.create(-15),
          weight: 1,
          occurredAt: now,
          createdAt: now,
          source: { type: 'adminAction', id: input.adminActionId },
          reason: {
            code: AdminTrustReasonCode.create('ADMIN_ACTION_ABUSE_REPORT_PENALTY').value,
            summary: 'Validated abuse report',
            details: input.details,
          },
          visibility: { public: false, redactedFields: ['reason.summary', 'reason.details'] },
          version: 1,
        })
      );
    } else if (input.actionType === 'rule_clarity') {
      events.push(
        RatingEvent.create({
          id: RatingEventId.generate(),
          userId: input.userId,
          dimension: RatingDimensionKey.create('adminTrust'),
          delta: RatingDelta.create(3),
          weight: 1,
          occurredAt: now,
          createdAt: now,
          source: { type: 'adminAction', id: input.adminActionId },
          reason: {
            code: AdminTrustReasonCode.create('ADMIN_ACTION_RULE_CLARITY_BONUS').value,
            summary: 'Published clear rules/changes',
            details: input.details,
          },
          visibility: { public: true, redactedFields: [] },
          version: 1,
        })
      );
    }

    return events;
  }

  /**
   * Calculate performance delta based on position and field strength
   */
  private static calculatePerformanceDelta(
    position: number,
    totalDrivers: number,
    startPosition: number,
    fieldStrength: number
  ): number {
    // Base score from position (reverse percentile)
    const positionScore = ((totalDrivers - position + 1) / totalDrivers) * 100;

    // Bonus for positions gained
    const positionsGained = startPosition - position;
    const gainBonus = Math.max(0, positionsGained * 2);

    // Field strength multiplier (higher field strength = harder competition)
    // Normalize field strength to 0.8-1.2 range
    const fieldMultiplier = 0.8 + Math.min(fieldStrength / 10000, 0.4);

    const rawScore = (positionScore + gainBonus) * fieldMultiplier;

    // Convert to delta (range -50 to +50)
    // 50th percentile = 0, top = +50, bottom = -50
    return Math.round(rawScore - 50);
  }

  /**
   * Get ordinal suffix for position
   */
  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';
  }

  /**
   * Get human-readable summary for status
   */
  private static getStatusSummary(status: 'finished' | 'dnf' | 'dns' | 'dsq' | 'afk'): string {
    switch (status) {
      case 'finished': return 'Race completed';
      case 'dnf': return 'Did not finish';
      case 'dns': return 'Did not start';
      case 'dsq': return 'Disqualified';
      case 'afk': return 'AFK / Not responsive';
    }
  }
}

// Type export for convenience
export type DrivingReasonCodeValue =
  | 'DRIVING_FINISH_STRENGTH_GAIN'
  | 'DRIVING_POSITIONS_GAINED_BONUS'
  | 'DRIVING_PACE_RELATIVE_GAIN'
  | 'DRIVING_INCIDENTS_PENALTY'
  | 'DRIVING_MAJOR_CONTACT_PENALTY'
  | 'DRIVING_PENALTY_INVOLVEMENT_PENALTY'
  | 'DRIVING_DNS_PENALTY'
  | 'DRIVING_DNF_PENALTY'
  | 'DRIVING_DSQ_PENALTY'
  | 'DRIVING_AFK_PENALTY'
  | 'DRIVING_SEASON_ATTENDANCE_BONUS';