import { and, eq, inArray, isNull } from "drizzle-orm";
import { database } from "~/database/context";
import * as schema from "~/database/schema";
import { batchUpsertParticipantEVs, syncVorpForSeason } from "~/models/participant-expected-value";
import { recalculateStandings } from "~/models/scoring-calculator";
import { createDailySnapshot } from "~/models/standings";
import { BracktSimulator } from "~/services/simulations/brackt-simulator";

export const BRACKT_SLUG = "brackt";

type Db = ReturnType<typeof database>;

function isBracktSport(sport: { slug: string } | null | undefined) {
  return sport?.slug === BRACKT_SLUG;
}

export async function seedOrRepairBracktTemplate(providedDb?: Db) {
  const db = providedDb || database();
  return await db.transaction(async (tx) => {
    const existingSport = await tx.query.sports.findFirst({
      where: eq(schema.sports.slug, BRACKT_SLUG),
    });

    const [sport] = existingSport
      ? await tx
          .update(schema.sports)
          .set({ name: "Brackt", type: "team", simulatorType: "brackt", updatedAt: new Date() })
          .where(eq(schema.sports.id, existingSport.id))
          .returning()
      : await tx
          .insert(schema.sports)
          .values({
            name: "Brackt",
            type: "team",
            slug: BRACKT_SLUG,
            description: "League meta-sport based on final Brackt standings",
            simulatorType: "brackt",
          })
          .returning();

    const existingTemplate = await tx.query.sportsSeasons.findFirst({
      where: and(eq(schema.sportsSeasons.sportId, sport.id), isNull(schema.sportsSeasons.fantasySeasonId)),
      orderBy: (sportsSeasons, { desc }) => [desc(sportsSeasons.year)],
    });

    const templateValues = {
      sportId: sport.id,
      name: "Brackt",
      year: 2026,
      startDate: "2026-01-01",
      endDate: "2099-12-31",
      status: "upcoming" as const,
      scoringType: "regular_season" as const,
      scoringPattern: "season_standings" as const,
      draftOn: "2026-01-01",
      draftOff: "2099-12-31",
      fantasySeasonId: null,
      updatedAt: new Date(),
    };

    const [template] = existingTemplate
      ? await tx
          .update(schema.sportsSeasons)
          .set(templateValues)
          .where(eq(schema.sportsSeasons.id, existingTemplate.id))
          .returning()
      : await tx.insert(schema.sportsSeasons).values(templateValues).returning();

    return { sport, template };
  });
}

export async function findPrivateBracktSeason(fantasySeasonId: string, providedDb?: Db) {
  const db = providedDb || database();
  return db.query.sportsSeasons.findFirst({
    where: eq(schema.sportsSeasons.fantasySeasonId, fantasySeasonId),
    with: { sport: true },
  }).then((season) => (isBracktSport(season?.sport) ? season : undefined));
}

async function ensurePrivateBracktSeason(fantasySeasonId: string, templateId: string, db: Db) {
  const existing = await findPrivateBracktSeason(fantasySeasonId, db);
  if (existing) return existing;

  const template = await db.query.sportsSeasons.findFirst({
    where: eq(schema.sportsSeasons.id, templateId),
    with: { sport: true },
  });
  if (!template || !isBracktSport(template.sport)) {
    throw new Error("Brackt template sports season not found");
  }

  const fantasySeason = await db.query.seasons.findFirst({
    where: eq(schema.seasons.id, fantasySeasonId),
    with: { league: true },
  });

  const [privateSeason] = await db
    .insert(schema.sportsSeasons)
    .values({
      sportId: template.sportId,
      fantasySeasonId,
      name: `${fantasySeason?.league.name ?? "League"} Brackt`,
      year: fantasySeason?.year ?? template.year,
      startDate: template.startDate,
      endDate: template.endDate,
      status: "upcoming",
      scoringType: "regular_season",
      scoringPattern: "season_standings",
      draftOn: template.draftOn,
      draftOff: template.draftOff,
    })
    .returning();

  return { ...privateSeason, sport: template.sport };
}

export async function syncPrivateBracktParticipants(fantasySeasonId: string, providedDb?: Db) {
  const db = providedDb || database();
  const bracktSeason = await findPrivateBracktSeason(fantasySeasonId, db);
  if (!bracktSeason) return null;

  const teams = await db.query.teams.findMany({
    where: eq(schema.teams.seasonId, fantasySeasonId),
    orderBy: (teamsTable, { asc }) => [asc(teamsTable.name)],
  });
  const participants = await db.query.seasonParticipants.findMany({
    where: eq(schema.seasonParticipants.sportsSeasonId, bracktSeason.id),
  });

  const participantByTeamId = new Map(participants.map((p) => [p.externalId, p]));
  const teamIds = new Set(teams.map((team) => team.id));

  for (const team of teams) {
    const participant = participantByTeamId.get(team.id);
    if (participant) {
      if (participant.name !== team.name || participant.shortName !== team.name) {
        await db
          .update(schema.seasonParticipants)
          .set({ name: team.name, shortName: team.name, updatedAt: new Date() })
          .where(eq(schema.seasonParticipants.id, participant.id));
      }
    } else {
      await db.insert(schema.seasonParticipants).values({
        sportsSeasonId: bracktSeason.id,
        name: team.name,
        shortName: team.name,
        externalId: team.id,
      });
    }
  }

  const obsolete = participants.filter((p) => p.externalId && !teamIds.has(p.externalId));
  if (obsolete.length > 0) {
    await db
      .delete(schema.seasonParticipants)
      .where(inArray(schema.seasonParticipants.id, obsolete.map((p) => p.id)));
  }

  await runBracktHarvilleForFantasySeason(fantasySeasonId, db);
  return bracktSeason;
}

export async function applyBracktSportsForSeason(
  fantasySeasonId: string,
  selectedSportsSeasonIds: string[],
  providedDb?: Db
): Promise<string[]> {
  const db = providedDb || database();
  const selected = [...new Set(selectedSportsSeasonIds)];
  if (selected.length === 0) return selected;

  const sportsSeasons = await db.query.sportsSeasons.findMany({
    where: inArray(schema.sportsSeasons.id, selected),
    with: { sport: true },
  });
  const bracktTemplate = sportsSeasons.find((ss) => isBracktSport(ss.sport) && ss.fantasySeasonId === null);
  const existingPrivate = await findPrivateBracktSeason(fantasySeasonId, db);
  const nonBracktIds = selected.filter((id) => {
    const ss = sportsSeasons.find((s) => s.id === id);
    return !ss || !isBracktSport(ss.sport);
  });

  if (!bracktTemplate && existingPrivate && !selected.includes(existingPrivate.id)) {
    await db.delete(schema.sportsSeasons).where(eq(schema.sportsSeasons.id, existingPrivate.id));
    return nonBracktIds;
  }

  if (!bracktTemplate) return selected;

  const privateSeason = await ensurePrivateBracktSeason(fantasySeasonId, bracktTemplate.id, db);
  await syncPrivateBracktParticipants(fantasySeasonId, db);
  return [...nonBracktIds, privateSeason.id];
}

export async function runBracktHarvilleForFantasySeason(fantasySeasonId: string, db: Db) {
  const bracktSeason = await findPrivateBracktSeason(fantasySeasonId, db);
  if (!bracktSeason) return [];

  const fantasySeason = await db.query.seasons.findFirst({
    where: eq(schema.seasons.id, fantasySeasonId),
  });
  if (!fantasySeason) return [];

  const simulator = new BracktSimulator(db);
  const results = await simulator.simulate(bracktSeason.id);
  await batchUpsertParticipantEVs(
    results.map((result) => ({
      participantId: result.participantId,
      sportsSeasonId: bracktSeason.id,
      probabilities: result.probabilities,
      scoringRules: {
        pointsFor1st: fantasySeason.pointsFor1st,
        pointsFor2nd: fantasySeason.pointsFor2nd,
        pointsFor3rd: fantasySeason.pointsFor3rd,
        pointsFor4th: fantasySeason.pointsFor4th,
        pointsFor5th: fantasySeason.pointsFor5th,
        pointsFor6th: fantasySeason.pointsFor6th,
        pointsFor7th: fantasySeason.pointsFor7th,
        pointsFor8th: fantasySeason.pointsFor8th,
      },
      source: "performance_model",
    })),
    db
  );
  await syncVorpForSeason(bracktSeason.id, db);

  const participants = await db.query.seasonParticipants.findMany({
    where: eq(schema.seasonParticipants.sportsSeasonId, bracktSeason.id),
  });
  return participants.map((p) => ({
    participantId: p.id,
    expectedValue: Number(p.expectedValue ?? 0),
    vorpValue: Number(p.vorpValue ?? 0),
  }));
}

async function resolveBracktForFantasySeason(
  fantasySeasonId: string,
  db: Db,
  trigger: "auto" | "manual",
  completedSportsSeasonId?: string
) {
  const bracktSeason = await findPrivateBracktSeason(fantasySeasonId, db);
  if (!bracktSeason) return { status: "skipped" as const, reason: "no_brackt" };

  const picks = await db.query.draftPicks.findMany({
    where: eq(schema.draftPicks.seasonId, fantasySeasonId),
    with: { participant: { with: { sportsSeason: { with: { sport: true } }, results: true } } },
  });
  const nonBracktPicks = picks.filter((pick) => !isBracktSport(pick.participant.sportsSeason.sport));
  const ready = nonBracktPicks.every((pick) => {
    const hasFinalizedResult = pick.participant.results.some(
      (r) => r.finalPosition !== null && !r.isPartialScore
    );
    return hasFinalizedResult || pick.participant.sportsSeason.status === "completed";
  });
  if (!ready) return { status: "skipped" as const, reason: "incomplete" };

  const [standings, bracktParticipants, fantasySeason] = await Promise.all([
    db.query.teamStandings.findMany({ where: eq(schema.teamStandings.seasonId, fantasySeasonId) }),
    db.query.seasonParticipants.findMany({ where: eq(schema.seasonParticipants.sportsSeasonId, bracktSeason.id) }),
    db.query.seasons.findFirst({ where: eq(schema.seasons.id, fantasySeasonId), with: { league: true } }),
  ]);
  if (standings.length === 0 || !fantasySeason) {
    return { status: "skipped" as const, reason: "missing_standings" };
  }

  const participantByTeamId = new Map(bracktParticipants.map((p) => [p.externalId, p]));
  const desired = standings.map((standing) => {
    const participant = participantByTeamId.get(standing.teamId);
    if (!participant) throw new Error(`Missing Brackt participant for team ${standing.teamId}`);
    return { participantId: participant.id, finalPosition: standing.currentRank };
  });

  const existingResults = await db.query.seasonParticipantResults.findMany({
    where: eq(schema.seasonParticipantResults.sportsSeasonId, bracktSeason.id),
  });
  if (existingResults.length > 0) {
    const existingByParticipantId = new Map(existingResults.map((r) => [r.participantId, r]));
    const differs = desired.some((placement) => {
      const existing = existingByParticipantId.get(placement.participantId);
      return existing && existing.finalPosition !== placement.finalPosition;
    });
    if (differs) {
      return { status: "blocked" as const, reason: "existing_results_differ" };
    }
  }

  const now = new Date();
  const existingByParticipantId = new Map(existingResults.map((r) => [r.participantId, r]));
  for (const placement of desired) {
    const existing = existingByParticipantId.get(placement.participantId);
    if (existing) {
      await db
        .update(schema.seasonParticipantResults)
        .set({
          finalPosition: placement.finalPosition,
          isPartialScore: false,
          updatedAt: now,
        })
        .where(eq(schema.seasonParticipantResults.id, existing.id));
    } else {
      await db.insert(schema.seasonParticipantResults).values({
        participantId: placement.participantId,
        sportsSeasonId: bracktSeason.id,
        finalPosition: placement.finalPosition,
        isPartialScore: false,
      });
    }
  }

  await db
    .update(schema.sportsSeasons)
    .set({ status: "completed", updatedAt: now })
    .where(eq(schema.sportsSeasons.id, bracktSeason.id));
  await recalculateStandings(fantasySeasonId, db);
  await createDailySnapshot(fantasySeasonId, db);
  await db.insert(schema.commissionerAuditLog).values({
    seasonId: fantasySeasonId,
    leagueId: fantasySeason.leagueId,
    actorUserId: trigger === "auto" ? "system" : "commissioner",
    actorDisplayName: trigger === "auto" ? "System" : "Commissioner",
    action: "brackt_resolved",
    affectedTeamIds: standings.map((s) => s.teamId),
    details: { trigger, completedSportsSeasonId },
  });

  return { status: "resolved" as const };
}

export async function maybeResolveCompletedBracktForSportsSeason(completedSportsSeasonId: string, providedDb?: Db) {
  const db = providedDb || database();
  const completedSeason = await db.query.sportsSeasons.findFirst({
    where: eq(schema.sportsSeasons.id, completedSportsSeasonId),
    with: { sport: true },
  });
  if (!completedSeason || isBracktSport(completedSeason.sport)) return [];

  const links = await db.query.seasonSports.findMany({
    where: eq(schema.seasonSports.sportsSeasonId, completedSportsSeasonId),
  });

  const results = [];
  for (const link of links) {
    results.push(await resolveBracktForFantasySeason(link.seasonId, db, "auto", completedSportsSeasonId));
  }
  return results;
}

