import { database } from "~/database/context";
import { eq } from "drizzle-orm";
import * as schema from "~/database/schema";
import { findParticipantsBySportsSeasonId, updateParticipant } from "~/models/participant";
import { upsertRegularSeasonStandings } from "~/models/regular-season-standings";
import { upsertPendingStandingsMappings } from "~/models/pending-standings-mappings";
import { findMatchingTeamName } from "~/lib/normalize-team-name";
import { NhlStandingsAdapter } from "./nhl";
import { NbaStandingsAdapter } from "./nba";
import { AflStandingsAdapter } from "./afl";
import type { StandingsSyncAdapter, SyncResult, UnmatchedTeam } from "./types";

/**
 * Map a simulator type to its standings sync adapter.
 * Returns null for sports that don't use regularSeasonStandings
 * (e.g. season_standings sports like F1 will use a different path when implemented).
 */
function getAdapter(simulatorType: string): StandingsSyncAdapter {
  switch (simulatorType) {
    case "nba_bracket":
      return new NbaStandingsAdapter();
    case "nhl_bracket":
      return new NhlStandingsAdapter();
    case "afl_bracket":
      return new AflStandingsAdapter();
    case "f1_standings":
      throw new Error(
        "F1 standings sync is not yet implemented. Use the manual standings page."
      );
    case "indycar_standings":
      throw new Error(
        "IndyCar standings sync is not yet implemented. Use the manual standings page."
      );
    default:
      throw new Error(
        `No standings sync adapter available for simulator type "${simulatorType}". ` +
          "Only NBA and NHL are currently supported."
      );
  }
}

/**
 * Sync current standings from the appropriate external API for a sports season.
 * Matches API team names to participants by name and bulk-upserts the results.
 *
 * Returns the count of synced teams and any API team names that couldn't be matched.
 */
export async function syncStandings(sportsSeasonId: string): Promise<SyncResult> {
  const db = database();

  // Load sports season with sport relation
  const sportsSeason = await db.query.sportsSeasons.findFirst({
    where: eq(schema.sportsSeasons.id, sportsSeasonId),
    with: { sport: true },
  });

  if (!sportsSeason) {
    throw new Error(`Sports season ${sportsSeasonId} not found`);
  }

  const simulatorType = sportsSeason.sport?.simulatorType;
  if (!simulatorType) {
    const sportName = sportsSeason.sport?.name ?? "(no sport linked)";
    throw new Error(`Sport "${sportName}" has no simulator type configured`);
  }

  const adapter = getAdapter(simulatorType);
  const fetchedRecords = await adapter.fetchStandings();

  // Load all participants for this season
  const participants = await findParticipantsBySportsSeasonId(sportsSeasonId);
  const participantNames = participants.map((p) => p.name);
  const participantByName = new Map(participants.map((p) => [p.name, p]));
  // Build a map of externalId → participant for ID-first matching
  const participantByExternalId = new Map(
    participants.filter((p) => p.externalId).map((p) => [p.externalId ?? "", p])
  );

  const toUpsert: Parameters<typeof upsertRegularSeasonStandings>[0] = [];
  const unmatchedRecords: typeof fetchedRecords = [];

  for (const record of fetchedRecords) {
    // Strategy A: try externalId-first (bypasses name matching entirely after first sync)
    let participant = participantByExternalId.get(record.externalTeamId) ?? null;

    if (!participant) {
      // Fall back to name matching
      const matchedName = findMatchingTeamName(record.teamName, participantNames);
      if (matchedName) {
        participant = participantByName.get(matchedName) ?? null;
        // Write-back: persist externalId so future syncs skip name matching for this team
        if (participant && !participant.externalId) {
          await updateParticipant(participant.id, { externalId: record.externalTeamId });
        }
      }
    }

    if (!participant) {
      unmatchedRecords.push(record);
      continue;
    }

    toUpsert.push({
      participantId: participant.id,
      sportsSeasonId,
      wins: record.wins,
      losses: record.losses,
      otLosses: record.otLosses ?? null,
      ties: record.ties ?? null,
      winPct: record.winPct,
      gamesPlayed: record.gamesPlayed,
      gamesBack: record.gamesBack ?? null,
      conference: record.conference ?? null,
      division: record.division ?? null,
      conferenceRank: record.conferenceRank ?? null,
      divisionRank: record.divisionRank ?? null,
      leagueRank: record.leagueRank,
      streak: record.streak ?? null,
      lastTen: record.lastTen ?? null,
      homeRecord: record.homeRecord ?? null,
      awayRecord: record.awayRecord ?? null,
      externalTeamId: record.externalTeamId,
      syncedAt: new Date(),
    });
  }

  if (toUpsert.length > 0) {
    await upsertRegularSeasonStandings(toUpsert);
  }

  // Persist unmatched records so admin can resolve them without losing data on page reload
  if (unmatchedRecords.length > 0) {
    await upsertPendingStandingsMappings(sportsSeasonId, unmatchedRecords);
  }

  const unmatched: UnmatchedTeam[] = unmatchedRecords.map((r) => ({
    teamName: r.teamName,
    externalTeamId: r.externalTeamId,
  }));

  return { synced: toUpsert.length, unmatched };
}
