import { eq, and } from "drizzle-orm";
import { database } from "~/database/context";
import * as schema from "~/database/schema";
import { processQualifyingEvent } from "~/models/scoring-calculator";

export interface SyncReport {
  tournamentId: string;
  windowsSynced: number;
  windowsFailed: number;
  failures: Array<{
    scoringEventId: string;
    sportsSeasonId: string;
    error: string;
  }>;
}

/**
 * Fan out canonical tournament_results to every scoring_event window that
 * points at this tournament. For each linked window:
 *   1. Upsert event_results rows mirroring canonical placement / rawScore for
 *      every roster participant that appears in the canonical results.
 *   2. Write 0-QP filler rows for roster participants who have no result.
 *   3. Delegate to processQualifyingEvent to compute QP.
 *
 * Each window runs in its own transaction so partial failures isolate.
 * Errors in one window never interrupt sync of the remaining windows.
 *
 * This service NEVER writes qualifying_points_awarded directly (except the
 * "0" filler for participants with no placement) — QP calculation for real
 * results is owned by processQualifyingEvent.
 */
export async function syncTournamentResults(
  tournamentId: string
): Promise<SyncReport> {
  const db = database();

  const report: SyncReport = {
    tournamentId,
    windowsSynced: 0,
    windowsFailed: 0,
    failures: [],
  };

  // 1. Load canonical results for this tournament.
  const canonicalResults = await db
    .select()
    .from(schema.tournamentResults)
    .where(eq(schema.tournamentResults.tournamentId, tournamentId));

  // 2. Load every scoring_event that points at this tournament.
  const linkedEvents = await db
    .select()
    .from(schema.scoringEvents)
    .where(eq(schema.scoringEvents.tournamentId, tournamentId));

  // 3. Fan out — each event gets its own transaction.
  for (const ev of linkedEvents) {
    try {
      await db.transaction(async (tx: typeof db) => {
        // 3a. Window roster = season_participants for this event's sports_season.
        const rosters = await tx
          .select()
          .from(schema.seasonParticipants)
          .where(eq(schema.seasonParticipants.sportsSeasonId, ev.sportsSeasonId));

        // 3b. For each canonical result whose participant is on the roster,
        // upsert the matching event_results row. Only placement / rawScore
        // are copied; qualifying_points_awarded is left for
        // processQualifyingEvent to recompute.
        for (const cr of canonicalResults) {
          const sp = rosters.find((r) => r.participantId === cr.participantId);
          if (!sp) continue;

          const [existing] = await tx
            .select()
            .from(schema.eventResults)
            .where(
              and(
                eq(schema.eventResults.scoringEventId, ev.id),
                eq(schema.eventResults.seasonParticipantId, sp.id)
              )
            );

          if (existing) {
            await tx
              .update(schema.eventResults)
              .set({
                placement: cr.placement,
                rawScore: cr.rawScore,
                updatedAt: new Date(),
              })
              .where(eq(schema.eventResults.id, existing.id));
          } else {
            await tx.insert(schema.eventResults).values({
              scoringEventId: ev.id,
              seasonParticipantId: sp.id,
              placement: cr.placement,
              rawScore: cr.rawScore,
            });
          }
        }

        // 3c. 0-QP filler pass: every roster member without an event_results
        // row for this event gets one with placement=null, QP=0. This matches
        // the behavior from the per-window batch-qualifying-results design.
        const afterRows = await tx
          .select()
          .from(schema.eventResults)
          .where(eq(schema.eventResults.scoringEventId, ev.id));
        const covered = new Set(afterRows.map((r) => r.seasonParticipantId));
        for (const sp of rosters) {
          if (covered.has(sp.id)) continue;
          await tx.insert(schema.eventResults).values({
            scoringEventId: ev.id,
            seasonParticipantId: sp.id,
            placement: null,
            qualifyingPointsAwarded: "0",
          });
        }

        // 3d. Delegate to scoring engine inside the same transaction.
        await processQualifyingEvent(ev.id, tx);
      });
      report.windowsSynced += 1;
    } catch (e: unknown) {
      report.windowsFailed += 1;
      const msg =
        e instanceof Error ? e.message : typeof e === "string" ? e : String(e);
      report.failures.push({
        scoringEventId: ev.id,
        sportsSeasonId: ev.sportsSeasonId,
        error: msg,
      });
    }
  }

  return report;
}
