import type { ChampionshipConfig } from '../types/ChampionshipConfig';
import type { ParticipantRef } from '../types/ParticipantRef';
import { ChampionshipStanding } from '../entities/championship/ChampionshipStanding';
import type { ParticipantEventPoints } from './EventScoringService';
import { DropScoreApplier, type EventPointsEntry } from './DropScoreApplier';

export class ChampionshipAggregator {
  constructor(private readonly dropScoreApplier: DropScoreApplier) {}

  aggregate(params: {
    seasonId: string;
    championship: ChampionshipConfig;
    eventPointsByEventId: Record<string, ParticipantEventPoints[]>;
  }): ChampionshipStanding[] {
    const { seasonId, championship, eventPointsByEventId } = params;

    const perParticipantEvents = new Map<
      string,
      { participant: ParticipantRef; events: EventPointsEntry[] }
    >();

    for (const [eventId, pointsList] of Object.entries(eventPointsByEventId)) {
      for (const entry of pointsList) {
        const key = entry.participant.id;
        const existing = perParticipantEvents.get(key);
        const eventEntry: EventPointsEntry = {
          eventId,
          points: entry.totalPoints,
        };

        if (existing) {
          existing.events.push(eventEntry);
        } else {
          perParticipantEvents.set(key, {
            participant: entry.participant,
            events: [eventEntry],
          });
        }
      }
    }

    const standings: ChampionshipStanding[] = [];

    for (const { participant, events } of perParticipantEvents.values()) {
      const dropResult = this.dropScoreApplier.apply(
        championship.dropScorePolicy,
        events,
      );

      const totalPoints = dropResult.totalPoints;
      const resultsCounted = dropResult.counted.length;
      const resultsDropped = dropResult.dropped.length;

      standings.push(
        ChampionshipStanding.create({
          seasonId,
          championshipId: championship.id,
          participant,
          totalPoints,
          resultsCounted,
          resultsDropped,
          position: 0,
        }),
      );
    }

    standings.sort((a, b) => b.totalPoints.toNumber() - a.totalPoints.toNumber());

    return standings.map((s, index) => s.withPosition(index + 1));
  }
}