import type { ChampionshipConfig } from '../types/ChampionshipConfig';
import type { SessionType } from '../types/SessionType';
import type { ParticipantRef } from '../types/ParticipantRef';
import type { Result } from '../entities/result/Result';
import type { Penalty } from '../entities/Penalty';
import type { BonusRule } from '../types/BonusRule';
import type { ChampionshipType } from '../types/ChampionshipType';

import type { PointsTable } from '../value-objects/PointsTable';
import type { IDomainCalculationService } from '@core/shared/domain';

export interface ParticipantEventPoints {
  participant: ParticipantRef;
  basePoints: number;
  bonusPoints: number;
  penaltyPoints: number;
  totalPoints: number;
}

export interface EventScoringInput {
  seasonId: string;
  championship: ChampionshipConfig;
  sessionType: SessionType;
  results: Result[];
  penalties: Penalty[];
}

function createDriverParticipant(driverId: string): ParticipantRef {
  return {
    type: 'driver' as ChampionshipType,
    id: driverId,
  };
}

export class EventScoringService
  implements IDomainCalculationService<EventScoringInput, ParticipantEventPoints[]>
{
  calculate(input: EventScoringInput): ParticipantEventPoints[] {
    return this.scoreSession(input);
  }

  scoreSession(params: EventScoringInput): ParticipantEventPoints[] {
    const { championship, sessionType, results } = params;

    const pointsTable = this.getPointsTableForSession(championship, sessionType);
    const bonusRules = this.getBonusRulesForSession(championship, sessionType);

    const baseByDriver = new Map<string, number>();
    const bonusByDriver = new Map<string, number>();
    const penaltyByDriver = new Map<string, number>();

    for (const result of results) {
      const driverId = result.driverId.toString();
      const currentBase = baseByDriver.get(driverId) ?? 0;
      const added = pointsTable.getPointsForPosition(result.position.toNumber());
      baseByDriver.set(driverId, currentBase + added);
    }

    const fastestLapRule = bonusRules.find((r) => r.type === 'fastestLap');
    if (fastestLapRule) {
      this.applyFastestLapBonus(fastestLapRule, results, bonusByDriver);
    }

    const penaltyMap = this.aggregatePenalties(params.penalties);
    for (const [driverId, value] of penaltyMap.entries()) {
      penaltyByDriver.set(driverId, value);
    }

    const allDriverIds = new Set<string>();
    for (const id of baseByDriver.keys()) allDriverIds.add(id);
    for (const id of bonusByDriver.keys()) allDriverIds.add(id);
    for (const id of penaltyByDriver.keys()) allDriverIds.add(id);

    const participants: ParticipantEventPoints[] = [];
    for (const driverId of allDriverIds) {
      const basePoints = baseByDriver.get(driverId) ?? 0;
      const bonusPoints = bonusByDriver.get(driverId) ?? 0;
      const penaltyPoints = penaltyByDriver.get(driverId) ?? 0;
      const totalPoints = basePoints + bonusPoints - penaltyPoints;

      participants.push({
        participant: createDriverParticipant(driverId),
        basePoints,
        bonusPoints,
        penaltyPoints,
        totalPoints,
      });
    }

    return participants;
  }

  private getPointsTableForSession(
    championship: ChampionshipConfig,
    sessionType: SessionType,
  ): PointsTable {
    return championship.pointsTableBySessionType[sessionType];
  }

  private getBonusRulesForSession(
    championship: ChampionshipConfig,
    sessionType: SessionType,
  ): BonusRule[] {
    const all = championship.bonusRulesBySessionType ?? {};
    return (all as Record<SessionType, BonusRule[]>)[sessionType] ?? [];
  }

  private applyFastestLapBonus(
    rule: BonusRule,
    results: Result[],
    bonusByDriver: Map<string, number>,
  ): void {
    if (results.length === 0) return;

    const sortedByLap = [...results].sort((a, b) => a.fastestLap.toNumber() - b.fastestLap.toNumber());
    const best = sortedByLap[0];

    if (!best) {
      return;
    }

    const requiresTop = rule.requiresFinishInTopN;
    if (typeof requiresTop === 'number') {
      if (best.position.toNumber() <= 0 || best.position.toNumber() > requiresTop) {
        return;
      }
    }

    const current = bonusByDriver.get(best.driverId.toString()) ?? 0;
    bonusByDriver.set(best.driverId.toString(), current + rule.points);
  }

  private aggregatePenalties(penalties: Penalty[]): Map<string, number> {
    const map = new Map<string, number>();
    for (const penalty of penalties) {
      // Only count applied points_deduction penalties
      if (penalty.status !== 'applied' || penalty.type !== 'points_deduction') {
        continue;
      }
      
      const current = map.get(penalty.driverId) ?? 0;
      const delta = penalty.value ?? 0;
      map.set(penalty.driverId, current + delta);
    }
    return map;
  }
}