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

/**
 * Input DTO for driving rating calculation from race facts
 */
export interface DrivingRaceFactsDto {
  raceId: string;
  results: Array<{
    userId: string;
    startPos: number;
    finishPos: number;
    incidents: number;
    status: 'finished' | 'dnf' | 'dns' | 'dsq' | 'afk';
    sof?: number; // Optional: strength of field (platform ratings or external)
  }>;
}

/**
 * Individual driver calculation result
 */
export interface DriverCalculationResult {
  userId: string;
  delta: number;
  events: Array<{
    reasonCode: string;
    delta: number;
    weight: number;
    summary: string;
    details: Record<string, unknown>;
  }>;
}

/**
 * Domain Service: DrivingRatingCalculator
 *
 * Pure, stateless calculator for driving rating.
 * Implements full logic per ratings-architecture-concept.md section 5.1.
 *
 * Key principles:
 * - Performance: position vs field strength
 * - Clean driving: incident penalties
 * - Reliability: DNS/DNF/DSQ/AFK penalties
 * - Weighted by event recency and confidence
 */
export class DrivingRatingCalculator {
  // Weights for different components (sum to 1.0)
  private static readonly PERFORMANCE_WEIGHT = 0.5;
  private static readonly CLEAN_DRIVING_WEIGHT = 0.3;
  private static readonly RELIABILITY_WEIGHT = 0.2;

  // Penalty values for reliability issues
  private static readonly DNS_PENALTY = -15;
  private static readonly DNF_PENALTY = -10;
  private static readonly DSQ_PENALTY = -25;
  private static readonly AFK_PENALTY = -20;

  // Incident penalty per incident
  private static readonly INCIDENT_PENALTY = -5;
  private static readonly MAJOR_INCIDENT_PENALTY = -15;

  /**
   * Calculate driving rating deltas from race facts
   * Returns per-driver results with detailed event breakdown
   */
  static calculateFromRaceFacts(facts: DrivingRaceFactsDto): Map<string, DriverCalculationResult> {
    const results = new Map<string, DriverCalculationResult>();

    // Calculate field strength if not provided
    const fieldStrength = facts.results.length > 0
      ? (facts.results
          .filter(r => r.status === 'finished')
          .reduce((sum, r) => sum + (r.sof || this.estimateDriverRating(r.userId)), 0) /
          Math.max(1, facts.results.filter(r => r.status === 'finished').length))
      : 0;

    for (const result of facts.results) {
      const calculation = this.calculateDriverResult(result, fieldStrength, facts.results.length);
      results.set(result.userId, calculation);
    }

    return results;
  }

  /**
   * Calculate delta from existing rating events (for snapshot recomputation)
   * This is the "pure" calculation that sums weighted deltas
   */
  static calculate(events: RatingEvent[]): number {
    if (events.length === 0) return 0;

    // Group events by type and apply weights
    let totalDelta = 0;
    let performanceWeight = 0;
    let cleanDrivingWeight = 0;
    let reliabilityWeight = 0;

    for (const event of events) {
      const reasonCode = event.reason.code;
      const delta = event.delta.value;
      const weight = event.weight || 1;

      let componentWeight = 1;

      if (this.isPerformanceEvent(reasonCode)) {
        componentWeight = this.PERFORMANCE_WEIGHT;
        performanceWeight += weight;
      } else if (this.isCleanDrivingEvent(reasonCode)) {
        componentWeight = this.CLEAN_DRIVING_WEIGHT;
        cleanDrivingWeight += weight;
      } else if (this.isReliabilityEvent(reasonCode)) {
        componentWeight = this.RELIABILITY_WEIGHT;
        reliabilityWeight += weight;
      }

      // Apply component weight and event weight
      totalDelta += delta * componentWeight * weight;
    }

    // Normalize by total weight to prevent inflation
    const totalWeight = performanceWeight + cleanDrivingWeight + reliabilityWeight;
    if (totalWeight > 0) {
      totalDelta = totalDelta / totalWeight;
    }

    return Math.round(totalDelta * 100) / 100; // Round to 2 decimal places
  }

  /**
   * Calculate result for a single driver
   */
  private static calculateDriverResult(
    result: {
      userId: string;
      startPos: number;
      finishPos: number;
      incidents: number;
      status: 'finished' | 'dnf' | 'dns' | 'dsq' | 'afk';
      sof?: number;
    },
    fieldStrength: number,
    totalDrivers: number
  ): DriverCalculationResult {
    const events: Array<{
      reasonCode: string;
      delta: number;
      weight: number;
      summary: string;
      details: Record<string, unknown>;
    }> = [];

    let totalDelta = 0;

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

      if (performanceDelta !== 0) {
        events.push({
          reasonCode: 'DRIVING_FINISH_STRENGTH_GAIN',
          delta: performanceDelta,
          weight: 1,
          summary: `Finished ${result.finishPos}${this.getOrdinalSuffix(result.finishPos)} in field of ${totalDrivers}`,
          details: {
            startPos: result.startPos,
            finishPos: result.finishPos,
            positionsGained: result.startPos - result.finishPos,
            fieldStrength: fieldStrength,
            totalDrivers: totalDrivers,
          },
        });
        totalDelta += performanceDelta * this.PERFORMANCE_WEIGHT;

        // Positions gained bonus
        const positionsGained = result.startPos - result.finishPos;
        if (positionsGained > 0) {
          const gainBonus = Math.min(positionsGained * 2, 10);
          events.push({
            reasonCode: 'DRIVING_POSITIONS_GAINED_BONUS',
            delta: gainBonus,
            weight: 0.5,
            summary: `Gained ${positionsGained} positions`,
            details: { positionsGained },
          });
          totalDelta += gainBonus * this.PERFORMANCE_WEIGHT * 0.5;
        }
      }
    }

    // 2. Clean driving calculation
    if (result.incidents > 0) {
      const incidentPenalty = Math.min(result.incidents * this.INCIDENT_PENALTY, -30);
      events.push({
        reasonCode: 'DRIVING_INCIDENTS_PENALTY',
        delta: incidentPenalty,
        weight: 1,
        summary: `${result.incidents} incident${result.incidents > 1 ? 's' : ''}`,
        details: { incidents: result.incidents },
      });
      totalDelta += incidentPenalty * this.CLEAN_DRIVING_WEIGHT;
    }

    // 3. Reliability calculation
    if (result.status !== 'finished') {
      let reliabilityDelta = 0;
      let reasonCode = '';

      switch (result.status) {
        case 'dns':
          reliabilityDelta = this.DNS_PENALTY;
          reasonCode = 'DRIVING_DNS_PENALTY';
          break;
        case 'dnf':
          reliabilityDelta = this.DNF_PENALTY;
          reasonCode = 'DRIVING_DNF_PENALTY';
          break;
        case 'dsq':
          reliabilityDelta = this.DSQ_PENALTY;
          reasonCode = 'DRIVING_DSQ_PENALTY';
          break;
        case 'afk':
          reliabilityDelta = this.AFK_PENALTY;
          reasonCode = 'DRIVING_AFK_PENALTY';
          break;
      }

      events.push({
        reasonCode,
        delta: reliabilityDelta,
        weight: 1,
        summary: this.getStatusSummary(result.status),
        details: { status: result.status },
      });
      totalDelta += reliabilityDelta * this.RELIABILITY_WEIGHT;
    }

    // Normalize total delta by component weights
    const componentsUsed = [
      result.status === 'finished' ? 1 : 0,
      result.incidents > 0 ? 1 : 0,
      result.status !== 'finished' ? 1 : 0,
    ].reduce((sum, val) => sum + val, 0);

    if (componentsUsed > 0) {
      // The totalDelta is already weighted, but we need to normalize
      // to ensure the final result is within reasonable bounds
      const maxPossible = 50; // Max positive
      const minPossible = -50; // Max negative
      totalDelta = Math.max(minPossible, Math.min(maxPossible, totalDelta));
    }

    return {
      userId: result.userId,
      delta: Math.round(totalDelta * 100) / 100,
      events,
    };
  }

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

    // Expected score (50th percentile baseline)
    const expectedScore = 50;

    // Field strength multiplier (higher = harder competition, bigger rewards)
    // Normalize to 0.8-2.0 range
    const fieldMultiplier = 0.8 + Math.min(fieldStrength / 2000, 1.2);

    // Performance delta: how much better/worse than expected
    let delta = (positionScore - expectedScore) * fieldMultiplier;

    // Bonus for positions gained/lost
    const positionsGained = startPos - finishPos;
    delta += positionsGained * 2;

    // Clamp to reasonable range
    return Math.max(-30, Math.min(30, delta));
  }

  /**
   * Estimate driver rating for SoF calculation
   * This is a placeholder - in real implementation, would query user rating snapshot
   */
  private static estimateDriverRating(userId: string): number {
    // Default rating for new drivers
    return 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';
    }
  }

  /**
   * Check if reason code is performance-related
   */
  private static isPerformanceEvent(reasonCode: string): boolean {
    return reasonCode === 'DRIVING_FINISH_STRENGTH_GAIN' ||
           reasonCode === 'DRIVING_POSITIONS_GAINED_BONUS' ||
           reasonCode === 'DRIVING_PACE_RELATIVE_GAIN';
  }

  /**
   * Check if reason code is clean driving-related
   */
  private static isCleanDrivingEvent(reasonCode: string): boolean {
    return reasonCode === 'DRIVING_INCIDENTS_PENALTY' ||
           reasonCode === 'DRIVING_MAJOR_CONTACT_PENALTY' ||
           reasonCode === 'DRIVING_PENALTY_INVOLVEMENT_PENALTY';
  }

  /**
   * Check if reason code is reliability-related
   */
  private static isReliabilityEvent(reasonCode: string): boolean {
    return reasonCode === 'DRIVING_DNS_PENALTY' ||
           reasonCode === 'DRIVING_DNF_PENALTY' ||
           reasonCode === 'DRIVING_DSQ_PENALTY' ||
           reasonCode === 'DRIVING_AFK_PENALTY' ||
           reasonCode === 'DRIVING_SEASON_ATTENDANCE_BONUS';
  }
}