/**
 * Golf Skills model (canonical).
 *
 * Golf skills are stored in the canonical `participant_golf_skills` table,
 * keyed by canonical participant id. The admin UI still works in terms of
 * season_participants — we join through season_participants → canonical
 * participant → canonical golf skills so the UI doesn't need to know about
 * the canonical layer.
 *
 * Mirrors the surface-elo.ts pattern exactly.
 */

import { database } from "~/database/context";
import { participantGolfSkills, seasonParticipants } from "~/database/schema";
import { eq, inArray, sql } from "drizzle-orm";

export interface GolfSkillsRecord {
  id: string;
  participantId: string;
  sportsSeasonId: string;
  sgTotal: number | null;
  datagolfRank: number | null;
  mastersOdds: number | null;
  usOpenOdds: number | null;
  openChampionshipOdds: number | null;
  pgaChampionshipOdds: number | null;
  updatedAt: Date;
}

export interface GolfSkillsWithName extends GolfSkillsRecord {
  participantName: string;
}

export interface GolfSkillsInput {
  participantId: string;
  sgTotal?: number | null;
  datagolfRank?: number | null;
  mastersOdds?: number | null;
  usOpenOdds?: number | null;
  openChampionshipOdds?: number | null;
  pgaChampionshipOdds?: number | null;
}

/**
 * Load golf skills for a sports season's roster, joined with the per-window
 * participant's name. Internally joins season_participants → canonical
 * participants → canonical participant_golf_skills.
 *
 * The returned `id` is the canonical `participant_golf_skills.id`;
 * `participantId` is the season_participant id (admin UI keys rows by that).
 */
export async function getGolfSkillsForSeason(
  sportsSeasonId: string,
): Promise<GolfSkillsWithName[]> {
  const db = database();
  const rows = await db
    .select({
      id: participantGolfSkills.id,
      participantId: seasonParticipants.id,
      sportsSeasonId: seasonParticipants.sportsSeasonId,
      sgTotal: participantGolfSkills.sgTotal,
      datagolfRank: participantGolfSkills.datagolfRank,
      mastersOdds: participantGolfSkills.mastersOdds,
      usOpenOdds: participantGolfSkills.usOpenOdds,
      openChampionshipOdds: participantGolfSkills.openChampionshipOdds,
      pgaChampionshipOdds: participantGolfSkills.pgaChampionshipOdds,
      updatedAt: participantGolfSkills.updatedAt,
      participantName: seasonParticipants.name,
    })
    .from(seasonParticipants)
    .leftJoin(
      participantGolfSkills,
      eq(participantGolfSkills.participantId, seasonParticipants.participantId),
    )
    .where(eq(seasonParticipants.sportsSeasonId, sportsSeasonId))
    .orderBy(seasonParticipants.name);

  return rows
    .filter((r): r is typeof r & { id: string; updatedAt: Date } => r.id !== null)
    .map((r) => ({
      ...r,
      sgTotal: r.sgTotal !== null ? Number(r.sgTotal) : null,
    })) as GolfSkillsWithName[];
}

/**
 * Returns a Map from season_participant.id → GolfSkillsRecord for use in the simulator.
 * Internally joins through canonical participants to read from the canonical table.
 * Participants with no canonical link or no canonical golf skills record are absent
 * from the map (simulator falls back to SG = 0).
 */
export async function getGolfSkillsMap(
  sportsSeasonId: string,
): Promise<Map<string, GolfSkillsRecord>> {
  const db = database();
  const rows = await db
    .select({
      seasonParticipantId: seasonParticipants.id,
      sportsSeasonId: seasonParticipants.sportsSeasonId,
      id: participantGolfSkills.id,
      sgTotal: participantGolfSkills.sgTotal,
      datagolfRank: participantGolfSkills.datagolfRank,
      mastersOdds: participantGolfSkills.mastersOdds,
      usOpenOdds: participantGolfSkills.usOpenOdds,
      openChampionshipOdds: participantGolfSkills.openChampionshipOdds,
      pgaChampionshipOdds: participantGolfSkills.pgaChampionshipOdds,
      updatedAt: participantGolfSkills.updatedAt,
    })
    .from(seasonParticipants)
    .innerJoin(
      participantGolfSkills,
      eq(participantGolfSkills.participantId, seasonParticipants.participantId),
    )
    .where(eq(seasonParticipants.sportsSeasonId, sportsSeasonId));

  return new Map(rows.map((r) => [
    r.seasonParticipantId,
    {
      id: r.id,
      participantId: r.seasonParticipantId,
      sportsSeasonId: r.sportsSeasonId,
      sgTotal: r.sgTotal !== null ? Number(r.sgTotal) : null,
      datagolfRank: r.datagolfRank,
      mastersOdds: r.mastersOdds,
      usOpenOdds: r.usOpenOdds,
      openChampionshipOdds: r.openChampionshipOdds,
      pgaChampionshipOdds: r.pgaChampionshipOdds,
      updatedAt: r.updatedAt,
    },
  ]));
}

/**
 * Upsert golf skill ratings for a batch of season_participants. Writes to the
 * canonical `participant_golf_skills` table; callers pass season_participant
 * ids and we resolve canonical ids internally.
 *
 * season_participants without a canonical link are silently skipped. In
 * practice every qualifying-points roster entry is canonical-linked via
 * the auto-linking on createParticipant.
 */
export async function batchUpsertGolfSkills(
  inputs: GolfSkillsInput[],
): Promise<void> {
  if (inputs.length === 0) return;
  const db = database();
  const now = new Date();

  const sps = await db
    .select({ id: seasonParticipants.id, canonicalId: seasonParticipants.participantId })
    .from(seasonParticipants)
    .where(inArray(seasonParticipants.id, inputs.map((i) => i.participantId)));
  const canonicalByInputId = new Map(sps.map((sp) => [sp.id, sp.canonicalId]));

  const canonicalRows = inputs
    .map((i) => ({
      canonicalId: canonicalByInputId.get(i.participantId),
      sgTotal: i.sgTotal !== null && i.sgTotal !== undefined ? String(i.sgTotal) : null,
      datagolfRank: i.datagolfRank ?? null,
      mastersOdds: i.mastersOdds ?? null,
      usOpenOdds: i.usOpenOdds ?? null,
      openChampionshipOdds: i.openChampionshipOdds ?? null,
      pgaChampionshipOdds: i.pgaChampionshipOdds ?? null,
    }))
    .filter((r): r is typeof r & { canonicalId: string } => typeof r.canonicalId === "string");

  if (canonicalRows.length === 0) return;

  await db
    .insert(participantGolfSkills)
    .values(canonicalRows.map(({ canonicalId, ...rest }) => ({
      participantId: canonicalId,
      ...rest,
      updatedAt: now,
    })))
    .onConflictDoUpdate({
      target: [participantGolfSkills.participantId],
      set: {
        sgTotal: sql`excluded.sg_total`,
        datagolfRank: sql`excluded.datagolf_rank`,
        mastersOdds: sql`excluded.masters_odds`,
        usOpenOdds: sql`excluded.us_open_odds`,
        openChampionshipOdds: sql`excluded.open_championship_odds`,
        pgaChampionshipOdds: sql`excluded.pga_championship_odds`,
        updatedAt: sql`excluded.updated_at`,
      },
    });
}
