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

export interface TeamDrivingRaceResult {
  teamId: string;
  position: number;
  incidents: number;
  status: 'finished' | 'dnf' | 'dsq' | 'dns' | 'afk';
  fieldSize: number;
  strengthOfField: number; // Average rating of competing teams
  raceId: string;
  pace?: number | undefined; // Optional: pace rating (0-100)
  consistency?: number | undefined; // Optional: consistency rating (0-100)
  teamwork?: number | undefined; // Optional: teamwork rating (0-100)
  sportsmanship?: number | undefined; // Optional: sportsmanship rating (0-100)
}

export interface TeamDrivingQualifyingResult {
  teamId: string;
  qualifyingPosition: number;
  fieldSize: number;
  raceId: string;
}

export interface TeamDrivingOvertakeStats {
  teamId: string;
  overtakes: number;
  successfulDefenses: number;
  raceId: string;
}

/**
 * Domain Service: TeamDrivingRatingCalculator
 *
 * Full calculator for team driving rating events.
 * Mirrors user slice 3 in core/racing/ with comprehensive driving dimension logic.
 * 
 * Pure domain logic - no persistence concerns.
 */
export class TeamDrivingRatingCalculator {
  /**
   * Calculate rating events from a team's race finish.
   * Generates comprehensive driving dimension events.
   */
  static calculateFromRaceFinish(result: TeamDrivingRaceResult): TeamRatingEvent[] {
    const events: TeamRatingEvent[] = [];
    const now = new Date();

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

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

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

      // 3. Pace rating (if provided)
      if (result.pace !== undefined) {
        const paceDelta = this.calculatePaceDelta(result.pace);
        if (paceDelta !== 0) {
          events.push(
            TeamRatingEvent.create({
              id: TeamRatingEventId.generate(),
              teamId: result.teamId,
              dimension: TeamRatingDimensionKey.create('driving'),
              delta: TeamRatingDelta.create(paceDelta),
              weight: 0.3,
              occurredAt: now,
              createdAt: now,
              source: { type: 'race', id: result.raceId },
              reason: {
                code: TeamDrivingReasonCode.create('RACE_PACE').value,
                description: `Pace rating: ${result.pace}/100`,
              },
              visibility: { public: true },
              version: 1,
            })
          );
        }
      }

      // 4. Consistency rating (if provided)
      if (result.consistency !== undefined) {
        const consistencyDelta = this.calculateConsistencyDelta(result.consistency);
        if (consistencyDelta !== 0) {
          events.push(
            TeamRatingEvent.create({
              id: TeamRatingEventId.generate(),
              teamId: result.teamId,
              dimension: TeamRatingDimensionKey.create('driving'),
              delta: TeamRatingDelta.create(consistencyDelta),
              weight: 0.3,
              occurredAt: now,
              createdAt: now,
              source: { type: 'race', id: result.raceId },
              reason: {
                code: TeamDrivingReasonCode.create('RACE_CONSISTENCY').value,
                description: `Consistency rating: ${result.consistency}/100`,
              },
              visibility: { public: true },
              version: 1,
            })
          );
        }
      }

      // 5. Teamwork rating (if provided)
      if (result.teamwork !== undefined) {
        const teamworkDelta = this.calculateTeamworkDelta(result.teamwork);
        if (teamworkDelta !== 0) {
          events.push(
            TeamRatingEvent.create({
              id: TeamRatingEventId.generate(),
              teamId: result.teamId,
              dimension: TeamRatingDimensionKey.create('driving'),
              delta: TeamRatingDelta.create(teamworkDelta),
              weight: 0.4,
              occurredAt: now,
              createdAt: now,
              source: { type: 'race', id: result.raceId },
              reason: {
                code: TeamDrivingReasonCode.create('RACE_TEAMWORK').value,
                description: `Teamwork rating: ${result.teamwork}/100`,
              },
              visibility: { public: true },
              version: 1,
            })
          );
        }
      }

      // 6. Sportsmanship rating (if provided)
      if (result.sportsmanship !== undefined) {
        const sportsmanshipDelta = this.calculateSportsmanshipDelta(result.sportsmanship);
        if (sportsmanshipDelta !== 0) {
          events.push(
            TeamRatingEvent.create({
              id: TeamRatingEventId.generate(),
              teamId: result.teamId,
              dimension: TeamRatingDimensionKey.create('driving'),
              delta: TeamRatingDelta.create(sportsmanshipDelta),
              weight: 0.3,
              occurredAt: now,
              createdAt: now,
              source: { type: 'race', id: result.raceId },
              reason: {
                code: TeamDrivingReasonCode.create('RACE_SPORTSMANSHIP').value,
                description: `Sportsmanship rating: ${result.sportsmanship}/100`,
              },
              visibility: { public: true },
              version: 1,
            })
          );
        }
      }
    }

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

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

    return events;
  }

  /**
   * Calculate rating events from qualifying results.
   */
  static calculateFromQualifying(result: TeamDrivingQualifyingResult): TeamRatingEvent[] {
    const events: TeamRatingEvent[] = [];
    const now = new Date();

    const qualifyingDelta = this.calculateQualifyingDelta(result.qualifyingPosition, result.fieldSize);
    if (qualifyingDelta !== 0) {
      events.push(
        TeamRatingEvent.create({
          id: TeamRatingEventId.generate(),
          teamId: result.teamId,
          dimension: TeamRatingDimensionKey.create('driving'),
          delta: TeamRatingDelta.create(qualifyingDelta),
          weight: 0.25,
          occurredAt: now,
          createdAt: now,
          source: { type: 'race', id: result.raceId },
          reason: {
            code: TeamDrivingReasonCode.create('RACE_QUALIFYING').value,
            description: `Qualified ${result.qualifyingPosition}${this.getOrdinalSuffix(result.qualifyingPosition)}`,
          },
          visibility: { public: true },
          version: 1,
        })
      );
    }

    return events;
  }

  /**
   * Calculate rating events from overtake/defense statistics.
   */
  static calculateFromOvertakeStats(stats: TeamDrivingOvertakeStats): TeamRatingEvent[] {
    const events: TeamRatingEvent[] = [];
    const now = new Date();

    // Overtake bonus
    if (stats.overtakes > 0) {
      const overtakeDelta = this.calculateOvertakeDelta(stats.overtakes);
      events.push(
        TeamRatingEvent.create({
          id: TeamRatingEventId.generate(),
          teamId: stats.teamId,
          dimension: TeamRatingDimensionKey.create('driving'),
          delta: TeamRatingDelta.create(overtakeDelta),
          weight: 0.5,
          occurredAt: now,
          createdAt: now,
          source: { type: 'race', id: stats.raceId },
          reason: {
            code: TeamDrivingReasonCode.create('RACE_OVERTAKE').value,
            description: `${stats.overtakes} overtakes`,
          },
          visibility: { public: true },
          version: 1,
        })
      );
    }

    // Defense bonus
    if (stats.successfulDefenses > 0) {
      const defenseDelta = this.calculateDefenseDelta(stats.successfulDefenses);
      events.push(
        TeamRatingEvent.create({
          id: TeamRatingEventId.generate(),
          teamId: stats.teamId,
          dimension: TeamRatingDimensionKey.create('driving'),
          delta: TeamRatingDelta.create(defenseDelta),
          weight: 0.4,
          occurredAt: now,
          createdAt: now,
          source: { type: 'race', id: stats.raceId },
          reason: {
            code: TeamDrivingReasonCode.create('RACE_DEFENSE').value,
            description: `${stats.successfulDefenses} successful defenses`,
          },
          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 calculatePaceDelta(pace: number): number {
    // Pace rating 0-100, convert to delta -10 to +10
    if (pace < 0 || pace > 100) return 0;
    return Math.round(((pace - 50) * 0.2) * 10) / 10;
  }

  private static calculateConsistencyDelta(consistency: number): number {
    // Consistency rating 0-100, convert to delta -8 to +8
    if (consistency < 0 || consistency > 100) return 0;
    return Math.round(((consistency - 50) * 0.16) * 10) / 10;
  }

  private static calculateTeamworkDelta(teamwork: number): number {
    // Teamwork rating 0-100, convert to delta -10 to +10
    if (teamwork < 0 || teamwork > 100) return 0;
    return Math.round(((teamwork - 50) * 0.2) * 10) / 10;
  }

  private static calculateSportsmanshipDelta(sportsmanship: number): number {
    // Sportsmanship rating 0-100, convert to delta -8 to +8
    if (sportsmanship < 0 || sportsmanship > 100) return 0;
    return Math.round(((sportsmanship - 50) * 0.16) * 10) / 10;
  }

  private static calculateQualifyingDelta(qualifyingPosition: number, fieldSize: number): number {
    // Qualifying performance (less weight than race)
    const positionFactor = ((fieldSize - qualifyingPosition + 1) / fieldSize) * 10 - 5;
    return Math.round(positionFactor * 10) / 10;
  }

  private static calculateOvertakeDelta(overtakes: number): number {
    // Overtake bonus: +2 per overtake, max +10
    return Math.min(overtakes * 2, 10);
  }

  private static calculateDefenseDelta(defenses: number): number {
    // Defense bonus: +1.5 per defense, max +8
    return Math.min(Math.round(defenses * 1.5 * 10) / 10, 8);
  }

  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';
  }
}