import { eq, and } from "drizzle-orm";
import { database } from "~/database/context";
import * as schema from "~/database/schema";
import type { BracketTemplate, BracketRegion } from "~/lib/bracket-templates";
import {
  getBracketTemplate,
  buildNCAA68SlotMap,
  matchIndexForSeedSlot,
  STANDARD_BRACKET_SEEDING,
} from "~/lib/bracket-templates";

export type PlayoffMatch = typeof schema.playoffMatches.$inferSelect;
export type NewPlayoffMatch = typeof schema.playoffMatches.$inferInsert;

export async function createPlayoffMatch(
  data: NewPlayoffMatch
): Promise<PlayoffMatch> {
  const db = database();
  const [match] = await db
    .insert(schema.playoffMatches)
    .values(data)
    .returning();
  return match;
}

export async function createManyPlayoffMatches(
  data: NewPlayoffMatch[]
): Promise<PlayoffMatch[]> {
  const db = database();
  return await db
    .insert(schema.playoffMatches)
    .values(data)
    .returning();
}

export async function findPlayoffMatchById(
  id: string
): Promise<PlayoffMatch | undefined> {
  const db = database();
  return await db.query.playoffMatches.findFirst({
    where: eq(schema.playoffMatches.id, id),
    with: {
      scoringEvent: true,
      participant1: true,
      participant2: true,
      winner: true,
      loser: true,
      games: { orderBy: (g, { asc }) => [asc(g.gameNumber)] },
      odds: { with: { participant: true } },
    },
  });
}

export async function findPlayoffMatchesByEventId(
  eventId: string
): Promise<PlayoffMatch[]> {
  const db = database();
  return await db.query.playoffMatches.findMany({
    where: eq(schema.playoffMatches.scoringEventId, eventId),
    orderBy: (matches, { asc }) => [asc(matches.matchNumber)],
    with: {
      participant1: true,
      participant2: true,
      winner: true,
      loser: true,
      games: { orderBy: (g, { asc }) => [asc(g.gameNumber)] },
      odds: { with: { participant: true } },
    },
  });
}

export async function findPlayoffMatchesByEventIdAndRound(
  eventId: string,
  round: string
): Promise<PlayoffMatch[]> {
  const db = database();
  return await db.query.playoffMatches.findMany({
    where: and(
      eq(schema.playoffMatches.scoringEventId, eventId),
      eq(schema.playoffMatches.round, round)
    ),
    orderBy: (matches, { asc }) => [asc(matches.matchNumber)],
    with: {
      participant1: true,
      participant2: true,
      winner: true,
      loser: true,
    },
  });
}

export async function updatePlayoffMatch(
  id: string,
  data: Partial<NewPlayoffMatch>
): Promise<PlayoffMatch> {
  const db = database();
  const [match] = await db
    .update(schema.playoffMatches)
    .set({ ...data, updatedAt: new Date() })
    .where(eq(schema.playoffMatches.id, id))
    .returning();
  return match;
}

export async function deletePlayoffMatch(id: string): Promise<void> {
  const db = database();
  await db.delete(schema.playoffMatches).where(eq(schema.playoffMatches.id, id));
}

export async function deletePlayoffMatchesByEventId(
  eventId: string
): Promise<void> {
  const db = database();
  await db
    .delete(schema.playoffMatches)
    .where(eq(schema.playoffMatches.scoringEventId, eventId));
}

/**
 * Set the winner of a playoff match
 */
export async function setMatchWinner(
  matchId: string,
  winnerId: string,
  loserId: string,
  participant1Score?: number,
  participant2Score?: number
): Promise<PlayoffMatch> {
  const db = database();
  const [match] = await db
    .update(schema.playoffMatches)
    .set({
      winnerId,
      loserId,
      isComplete: true,
      participant1Score: participant1Score?.toString(),
      participant2Score: participant2Score?.toString(),
      updatedAt: new Date(),
    })
    .where(eq(schema.playoffMatches.id, matchId))
    .returning();
  return match;
}

/**
 * Generate a standard single elimination bracket structure
 */
export async function generateSingleEliminationBracket(
  eventId: string,
  participantIds: string[]
): Promise<PlayoffMatch[]> {
  const numParticipants = participantIds.length;

  // Validate that we have 4 or 8 participants for standard bracket
  if (numParticipants !== 4 && numParticipants !== 8) {
    throw new Error("Single elimination bracket requires 4 or 8 participants");
  }

  const matches: NewPlayoffMatch[] = [];

  if (numParticipants === 8) {
    // Quarterfinals (4 matches)
    for (let i = 0; i < 4; i++) {
      matches.push({
        scoringEventId: eventId,
        round: "Quarterfinals",
        matchNumber: i + 1,
        participant1Id: participantIds[i * 2],
        participant2Id: participantIds[i * 2 + 1],
        isComplete: false,
      });
    }

    // Semifinals (2 matches)
    for (let i = 0; i < 2; i++) {
      matches.push({
        scoringEventId: eventId,
        round: "Semifinals",
        matchNumber: i + 1,
        participant1Id: null,
        participant2Id: null,
        isComplete: false,
      });
    }
  } else if (numParticipants === 4) {
    // Semifinals (2 matches)
    for (let i = 0; i < 2; i++) {
      matches.push({
        scoringEventId: eventId,
        round: "Semifinals",
        matchNumber: i + 1,
        participant1Id: participantIds[i * 2],
        participant2Id: participantIds[i * 2 + 1],
        isComplete: false,
      });
    }
  }

  // Finals (1 match)
  matches.push({
    scoringEventId: eventId,
    round: "Finals",
    matchNumber: 1,
    participant1Id: null,
    participant2Id: null,
    isComplete: false,
  });

  return await createManyPlayoffMatches(matches);
}

/**
 * Advance the winner of a match to the next round
 * Uses deterministic slot assignment to maintain bracket structure
 */
export async function advanceWinner(
  matchId: string,
  winnerId: string
): Promise<void> {
  // Get the match
  const match = await findPlayoffMatchById(matchId);
  if (!match) throw new Error("Match not found");

  // Determine which round this is and where to advance
  const eventId = match.scoringEventId;
  let nextRound: string;
  let nextMatchNumber: number;
  let participantSlot: 'participant1Id' | 'participant2Id';

  if (match.round === "Quarterfinals") {
    nextRound = "Semifinals";
    // Match 1,2 -> SF1, Match 3,4 -> SF2
    nextMatchNumber = match.matchNumber <= 2 ? 1 : 2;
    // Odd matches (1, 3) -> participant1, Even matches (2, 4) -> participant2
    participantSlot = match.matchNumber % 2 === 1 ? 'participant1Id' : 'participant2Id';
  } else if (match.round === "Semifinals") {
    nextRound = "Finals";
    nextMatchNumber = 1;
    // SF Match 1 -> Finals participant1, SF Match 2 -> Finals participant2
    participantSlot = match.matchNumber === 1 ? 'participant1Id' : 'participant2Id';
  } else {
    // Finals - no advancement needed
    return;
  }

  // Find the next match
  const nextMatches = await findPlayoffMatchesByEventIdAndRound(eventId, nextRound);
  const nextMatch = nextMatches.find(m => m.matchNumber === nextMatchNumber);

  if (!nextMatch) throw new Error("Next match not found");

  // Check if the slot is already filled
  if (nextMatch[participantSlot]) {
    throw new Error(`Next match ${participantSlot} is already filled`);
  }

  // Fill the determined slot
  await updatePlayoffMatch(nextMatch.id, { [participantSlot]: winnerId });
}

/**
 * Generate standard tournament seeding matchups
 * Returns pairs of seed indices for proper bracket balance
 *
 * Examples:
 * - 4 teams: [[0,3], [1,2]] = 1v4, 2v3
 * - 8 teams: [[0,7], [3,4], [1,6], [2,5]] = 1v8, 4v5, 2v7, 3v6
 * - 16 teams: [[0,15], [7,8], [4,11], [3,12], [5,10], [2,13], [6,9], [1,14]]
 *            = 1v16, 8v9, 5v12, 4v13, 6v11, 3v14, 7v10, 2v15
 */
function generateStandardSeeding(teamCount: number): [number, number][] {
  if (![4, 8, 16, 32].includes(teamCount)) {
    // Non-standard sizes need sequential seeding for now
    const pairs: [number, number][] = [];
    for (let i = 0; i < teamCount; i += 2) {
      pairs.push([i, i + 1]);
    }
    return pairs;
  }

  // Standard balanced bracket seeding
  const rounds = Math.log2(teamCount);
  let seeds = [0, 1]; // Start with 1 vs 2

  // Build up seeding by adding rounds
  for (let round = 1; round < rounds; round++) {
    const newSeeds: number[] = [];
    const maxSeed = Math.pow(2, round + 1) - 1;

    for (const seed of seeds) {
      newSeeds.push(seed);
      newSeeds.push(maxSeed - seed);
    }
    seeds = newSeeds;
  }

  // Convert to pairs (odd indices vs even indices)
  const pairs: [number, number][] = [];
  for (let i = 0; i < seeds.length; i += 2) {
    pairs.push([seeds[i], seeds[i + 1]]);
  }

  return pairs;
}

/**
 * Generate a bracket from a template
 * Phase 2.6: Template-based bracket generation
 * Phase 2.7: Added proper tournament seeding
 *
 * @param eventId - The scoring event ID
 * @param templateId - The bracket template ID (e.g., "simple_16", "ncaa_68")
 * @param participantIds - Array of participant IDs to assign to the bracket (optional, can be assigned later)
 * @returns Array of created playoff matches
 */
export async function generateBracketFromTemplate(
  eventId: string,
  templateId: string,
  participantIds?: string[],
  regionOverride?: BracketRegion[]
): Promise<PlayoffMatch[]> {
  const template = getBracketTemplate(templateId);
  if (!template) {
    throw new Error(`Bracket template '${templateId}' not found`);
  }

  // Validate participant count if provided
  if (participantIds && participantIds.length !== template.totalTeams) {
    throw new Error(
      `Template '${templateId}' requires ${template.totalTeams} participants, but ${participantIds.length} were provided`
    );
  }

  // FIFA 48 generates an empty knockout bracket (participants assigned later via groups)
  if (templateId === "fifa_48") {
    return await generateFIFA48Bracket(eventId, template);
  }

  // NCAA 68 requires special handling for First Four and Round of 64
  // Use regionOverride if supplied (per-event config), otherwise fall back to template.regions
  if (templateId === "ncaa_68") {
    const effectiveTemplate = regionOverride
      ? { ...template, regions: regionOverride }
      : template;
    return await generateNCAA68Bracket(eventId, effectiveTemplate, participantIds);
  }

  // NFL 14 requires special handling for bye weeks
  if (templateId === "nfl_14") {
    return await generateNFL14Bracket(eventId, template, participantIds);
  }

  // AFL 10 requires special handling for double-chance finals system
  if (templateId === "afl_10") {
    return await generateAFL10Bracket(eventId, template, participantIds);
  }

  const matches: NewPlayoffMatch[] = [];

  // Generate matches for each round in the template
  for (const round of template.rounds) {
    // Only assign participants to the first round if participantIds provided
    let seedingPairs: [number, number][] = [];

    if (participantIds && round === template.rounds[0]) {
      const firstRoundTeams = round.matchCount * 2;
      seedingPairs = generateStandardSeeding(firstRoundTeams);
    }

    for (let i = 0; i < round.matchCount; i++) {
      let participant1Id: string | null = null;
      let participant2Id: string | null = null;
      let seedInfo: string | null = null;

      if (participantIds && round === template.rounds[0] && seedingPairs[i]) {
        const [seed1Index, seed2Index] = seedingPairs[i];
        participant1Id = participantIds[seed1Index] || null;
        participant2Id = participantIds[seed2Index] || null;

        // Store seed info for display (1-indexed for users)
        seedInfo = `${seed1Index + 1} vs ${seed2Index + 1}`;
      }

      matches.push({
        scoringEventId: eventId,
        round: round.name,
        matchNumber: i + 1,
        participant1Id,
        participant2Id,
        isComplete: false,
        isScoring: round.isScoring,
        templateRound: round.name,
        seedInfo,
      });
    }
  }

  return await createManyPlayoffMatches(matches);
}

/**
 * Generate NCAA 68 bracket using the template's regions config.
 *
 * Participant array layout (driven by buildNCAA68SlotMap):
 *   [region 0 direct seeds] [region 1 direct seeds] ...
 *   [play-in teams in region/play-in order, 2 per game]
 */
async function generateNCAA68Bracket(
  eventId: string,
  template: BracketTemplate,
  participantIds?: string[]
): Promise<PlayoffMatch[]> {
  if (!template.regions || template.regions.length === 0) {
    throw new Error("NCAA 68 template requires a regions config");
  }

  const matches: NewPlayoffMatch[] = [];
  const slotMap = buildNCAA68SlotMap(template.regions);

  // Per-region map: region index → FF labels for that region's play-ins (e.g. "FF1")
  const regionFFLabels = new Map<number, string[]>();
  for (let i = 0; i < slotMap.playInOffsets.length; i++) {
    const { regionIndex, playInIndex } = slotMap.playInOffsets[i];
    if (!regionFFLabels.has(regionIndex)) regionFFLabels.set(regionIndex, []);
    const labels = regionFFLabels.get(regionIndex);
    if (labels) labels[playInIndex] = `FF${i + 1}`;
  }

  // ── First Four ────────────────────────────────────────────────────────────
  for (let i = 0; i < slotMap.playInOffsets.length; i++) {
    const { startIndex, seedSlot, regionIndex } = slotMap.playInOffsets[i];
    const regionName = template.regions[regionIndex].name;
    matches.push({
      scoringEventId: eventId,
      round: "First Four",
      matchNumber: i + 1,
      participant1Id: participantIds ? (participantIds[startIndex] ?? null) : null,
      participant2Id: participantIds ? (participantIds[startIndex + 1] ?? null) : null,
      isComplete: false,
      isScoring: false,
      templateRound: "First Four",
      seedInfo: `${regionName} ${seedSlot}-seed play-in`,
    });
  }

  // ── Round of 64: 8 matches per region ────────────────────────────────────
  let r64MatchNumber = 1;
  for (let r = 0; r < template.regions.length; r++) {
    const region = template.regions[r];
    const directOffset = slotMap.directOffsets[r];
    const labels = regionFFLabels.get(r) ?? [];

    // Map seed rank → participant array index or FF label
    const seedToSlot = new Map<number, number | string>();
    for (let i = 0; i < region.directSeeds.length; i++) {
      seedToSlot.set(region.directSeeds[i], directOffset + i);
    }
    for (let p = 0; p < region.playIns.length; p++) {
      seedToSlot.set(region.playIns[p].seedSlot, labels[p]);
    }

    for (const [hiSeed, loSeed] of STANDARD_BRACKET_SEEDING) {
      const slot1 = seedToSlot.get(hiSeed);
      const slot2 = seedToSlot.get(loSeed);

      if (slot1 === undefined || slot2 === undefined) {
        throw new Error(
          `Region "${region.name}" missing seed ${slot1 === undefined ? hiSeed : loSeed}`
        );
      }

      matches.push({
        scoringEventId: eventId,
        round: "Round of 64",
        matchNumber: r64MatchNumber++,
        participant1Id:
          participantIds && typeof slot1 === "number"
            ? (participantIds[slot1] ?? null)
            : null,
        participant2Id:
          participantIds && typeof slot2 === "number"
            ? (participantIds[slot2] ?? null)
            : null,
        isComplete: false,
        isScoring: false,
        templateRound: "Round of 64",
        seedInfo: `${region.name}: ${hiSeed} vs ${typeof slot2 === "string" ? slot2 : loSeed}`,
      });
    }
  }

  // ── Round of 32 → Championship (empty match shells) ──────────────────────
  for (let roundIndex = 2; roundIndex < template.rounds.length; roundIndex++) {
    const round = template.rounds[roundIndex];
    for (let i = 0; i < round.matchCount; i++) {
      matches.push({
        scoringEventId: eventId,
        round: round.name,
        matchNumber: i + 1,
        participant1Id: null,
        participant2Id: null,
        isComplete: false,
        isScoring: round.isScoring,
        templateRound: round.name,
        seedInfo: null,
      });
    }
  }

  return await createManyPlayoffMatches(matches);
}

/**
 * Generate NFL 14 bracket with bye weeks for top 2 seeds
 * Phase 2.9: Special handling for Wild Card round (12 teams) and Divisional byes
 */
async function generateNFL14Bracket(
  eventId: string,
  template: BracketTemplate,
  participantIds?: string[]
): Promise<PlayoffMatch[]> {
  const matches: NewPlayoffMatch[] = [];

  // Actually for 14 teams (seeds 1-14), Wild Card should be:
  // Match 1: #7 (index 6) vs #10 (index 9)
  // Match 2: #6 (index 5) vs #11 (index 10)
  // Match 3: #5 (index 4) vs #12 (index 11)
  // Match 4: #8 (index 7) vs #9 (index 8)
  // Match 5: #4 (index 3) vs #13 (index 12)
  // Match 6: #3 (index 2) vs #14 (index 13)
  const wildCardSeeding = [
    [6, 9],   // #7 vs #10
    [5, 10],  // #6 vs #11
    [4, 11],  // #5 vs #12
    [7, 8],   // #8 vs #9
    [3, 12],  // #4 vs #13
    [2, 13],  // #3 vs #14
  ];

  for (let i = 0; i < wildCardSeeding.length; i++) {
    const [seed1, seed2] = wildCardSeeding[i];
    matches.push({
      scoringEventId: eventId,
      round: "Wild Card",
      matchNumber: i + 1,
      participant1Id: participantIds ? participantIds[seed1] : null,
      participant2Id: participantIds ? participantIds[seed2] : null,
      isComplete: false,
      isScoring: false, // Wild Card doesn't score fantasy points
      templateRound: "Wild Card",
      seedInfo: `${seed1 + 1} vs ${seed2 + 1}`,
    });
  }

  // Divisional: 4 games
  // Top 2 seeds (#1 and #2) get byes and are placed in Divisional automatically
  // The other 2 Divisional matches will be filled by Wild Card winners
  const divisionalRound = template.rounds.find((r) => r.name === "Divisional");
  if (divisionalRound) {
    for (let i = 0; i < divisionalRound.matchCount; i++) {
      let participant1Id: string | null = null;
      const participant2Id: string | null = null;
      let seedInfo: string | null = null;

      // Assign bye teams to first two Divisional matches
      if (participantIds) {
        if (i === 0) {
          // Match 1: #1 seed (index 0) vs TBD (Wild Card winner)
          participant1Id = participantIds[0];
          seedInfo = "1 vs TBD";
        } else if (i === 1) {
          // Match 2: #2 seed (index 1) vs TBD (Wild Card winner)
          participant1Id = participantIds[1];
          seedInfo = "2 vs TBD";
        }
        // Matches 3 and 4 are TBD vs TBD (Wild Card winners)
      }

      matches.push({
        scoringEventId: eventId,
        round: "Divisional",
        matchNumber: i + 1,
        participant1Id,
        participant2Id,
        isComplete: false,
        isScoring: divisionalRound.isScoring,
        templateRound: "Divisional",
        seedInfo,
      });
    }
  }

  // Generate remaining rounds (Conference Championship, Super Bowl)
  for (let roundIndex = 2; roundIndex < template.rounds.length; roundIndex++) {
    const round = template.rounds[roundIndex];
    for (let i = 0; i < round.matchCount; i++) {
      matches.push({
        scoringEventId: eventId,
        round: round.name,
        matchNumber: i + 1,
        participant1Id: null,
        participant2Id: null,
        isComplete: false,
        isScoring: round.isScoring,
        templateRound: round.name,
        seedInfo: null,
      });
    }
  }

  return await createManyPlayoffMatches(matches);
}

/**
 * Generate AFL 10 bracket with Wildcard Round (2026+ format)
 * Phase 3.3: Special handling for AFL's double-chance finals system
 *
 * Structure:
 * - Wildcard Round: 7v10, 8v9
 * - Qualifying Finals: 1v4, 2v3 (winners get bye to Preliminary Finals, losers to Semi-Finals)
 * - Elimination Finals: 5v8, 6v7 (where 7 and 8 are wildcard winners)
 * - Semi-Finals: QF losers vs EF winners
 * - Preliminary Finals: QF winners vs SF winners
 * - Grand Final: PF winners
 */
async function generateAFL10Bracket(
  eventId: string,
  template: BracketTemplate,
  participantIds?: string[]
): Promise<PlayoffMatch[]> {
  const matches: NewPlayoffMatch[] = [];

  // Wildcard Round: 7th vs 10th, 8th vs 9th
  const wildcardSeeding = [
    [6, 9],  // #7 (index 6) vs #10 (index 9) - 7th hosts
    [7, 8],  // #8 (index 7) vs #9 (index 8) - 8th hosts
  ];

  for (let i = 0; i < wildcardSeeding.length; i++) {
    const [seed1, seed2] = wildcardSeeding[i];
    matches.push({
      scoringEventId: eventId,
      round: "Wildcard Round",
      matchNumber: i + 1,
      participant1Id: participantIds ? participantIds[seed1] : null,
      participant2Id: participantIds ? participantIds[seed2] : null,
      isComplete: false,
      isScoring: false, // Losers get 0 points (9th-10th place)
      templateRound: "Wildcard Round",
      seedInfo: `${seed1 + 1} vs ${seed2 + 1}`,
    });
  }

  // Qualifying Finals: 1st vs 4th, 2nd vs 3rd
  const qualifyingSeeding = [
    [0, 3],  // #1 (index 0) vs #4 (index 3)
    [1, 2],  // #2 (index 1) vs #3 (index 2)
  ];

  for (let i = 0; i < qualifyingSeeding.length; i++) {
    const [seed1, seed2] = qualifyingSeeding[i];
    matches.push({
      scoringEventId: eventId,
      round: "Qualifying Finals",
      matchNumber: i + 1,
      participant1Id: participantIds ? participantIds[seed1] : null,
      participant2Id: participantIds ? participantIds[seed2] : null,
      isComplete: false,
      isScoring: false, // Winners get bye, losers get second chance
      templateRound: "Qualifying Finals",
      seedInfo: `${seed1 + 1} vs ${seed2 + 1}`,
    });
  }

  // Elimination Finals: 5th vs TBD (wildcard winner), 6th vs TBD (wildcard winner)
  const eliminationSeeding = [
    { higher: 4, wildcard: 2 },  // #5 (index 4) vs Wildcard Match 2 winner
    { higher: 5, wildcard: 1 },  // #6 (index 5) vs Wildcard Match 1 winner
  ];

  for (let i = 0; i < eliminationSeeding.length; i++) {
    const { higher, wildcard } = eliminationSeeding[i];
    matches.push({
      scoringEventId: eventId,
      round: "Elimination Finals",
      matchNumber: i + 1,
      participant1Id: participantIds ? participantIds[higher] : null,
      participant2Id: null, // Will be filled by wildcard winner
      isComplete: false,
      isScoring: true, // Losers share 7th-8th
      templateRound: "Elimination Finals",
      seedInfo: participantIds ? `${higher + 1} vs WC${wildcard}` : null,
    });
  }

  // Semi-Finals: QF losers vs EF winners (TBD vs TBD)
  for (let i = 0; i < 2; i++) {
    matches.push({
      scoringEventId: eventId,
      round: "Semi-Finals",
      matchNumber: i + 1,
      participant1Id: null, // Will be filled by QF loser
      participant2Id: null, // Will be filled by EF winner
      isComplete: false,
      isScoring: true, // Losers share 5th-6th
      templateRound: "Semi-Finals",
      seedInfo: null,
    });
  }

  // Preliminary Finals: QF winners vs SF winners (TBD vs TBD)
  for (let i = 0; i < 2; i++) {
    matches.push({
      scoringEventId: eventId,
      round: "Preliminary Finals",
      matchNumber: i + 1,
      participant1Id: null, // Will be filled by QF winner
      participant2Id: null, // Will be filled by SF winner
      isComplete: false,
      isScoring: true, // Losers share 3rd-4th
      templateRound: "Preliminary Finals",
      seedInfo: null,
    });
  }

  // Grand Final: PF winners (TBD vs TBD)
  matches.push({
    scoringEventId: eventId,
    round: "Grand Final",
    matchNumber: 1,
    participant1Id: null,
    participant2Id: null,
    isComplete: false,
    isScoring: true, // Winner 1st, Loser 2nd
    templateRound: "Grand Final",
    seedInfo: null,
  });

  return await createManyPlayoffMatches(matches);
}

/**
 * AFL-specific advancement logic for the complex double-chance system
 * Phase 3.3: Handles both winners and losers advancing to different rounds
 *
 * Advancement rules:
 * - Wildcard Round: Winner → Elimination Finals
 * - Qualifying Finals: Winner → Preliminary Finals, Loser → Semi-Finals
 * - Elimination Finals: Winner → Semi-Finals
 * - Semi-Finals: Winner → Preliminary Finals
 * - Preliminary Finals: Winner → Grand Final
 */
async function advanceAFLWinner(
  match: PlayoffMatch,
  winnerId: string,
  loserId: string
): Promise<void> {
  const eventId = match.scoringEventId;

  // Wildcard Round: Winner advances to Elimination Finals
  if (match.round === "Wildcard Round") {
    // Wildcard Match 1 winner → EF Match 2, participant2Id
    // Wildcard Match 2 winner → EF Match 1, participant2Id
    const efMatchNumber = match.matchNumber === 1 ? 2 : 1;
    const efMatches = await findPlayoffMatchesByEventIdAndRound(eventId, "Elimination Finals");
    const efMatch = efMatches.find((m) => m.matchNumber === efMatchNumber);

    if (!efMatch) throw new Error(`Elimination Finals match ${efMatchNumber} not found`);
    if (efMatch.participant2Id) throw new Error(`EF ${efMatchNumber} participant2 already filled`);

    await updatePlayoffMatch(efMatch.id, { participant2Id: winnerId });
    return;
  }

  // Qualifying Finals: Winner → Preliminary Finals, Loser → Semi-Finals
  if (match.round === "Qualifying Finals") {
    // QF Match 1: Winner → PF1 participant1, Loser → SF1 participant1
    // QF Match 2: Winner → PF2 participant1, Loser → SF2 participant1
    const pfMatchNumber = match.matchNumber;
    const pfMatches = await findPlayoffMatchesByEventIdAndRound(eventId, "Preliminary Finals");
    const pfMatch = pfMatches.find((m) => m.matchNumber === pfMatchNumber);

    if (!pfMatch) throw new Error(`Preliminary Finals match ${pfMatchNumber} not found`);
    if (pfMatch.participant1Id) throw new Error(`PF ${pfMatchNumber} participant1 already filled`);

    await updatePlayoffMatch(pfMatch.id, { participant1Id: winnerId });

    // Also advance loser to Semi-Finals
    const sfMatches = await findPlayoffMatchesByEventIdAndRound(eventId, "Semi-Finals");
    const sfMatch = sfMatches.find((m) => m.matchNumber === match.matchNumber);

    if (!sfMatch) throw new Error(`Semi-Finals match ${match.matchNumber} not found`);
    if (sfMatch.participant1Id) throw new Error(`SF ${match.matchNumber} participant1 already filled`);

    await updatePlayoffMatch(sfMatch.id, { participant1Id: loserId });
    return;
  }

  // Elimination Finals: Winner → Semi-Finals
  if (match.round === "Elimination Finals") {
    // EF Match 1 winner → SF2 participant2
    // EF Match 2 winner → SF1 participant2
    const sfMatchNumber = match.matchNumber === 1 ? 2 : 1;
    const sfMatches = await findPlayoffMatchesByEventIdAndRound(eventId, "Semi-Finals");
    const sfMatch = sfMatches.find((m) => m.matchNumber === sfMatchNumber);

    if (!sfMatch) throw new Error(`Semi-Finals match ${sfMatchNumber} not found`);
    if (sfMatch.participant2Id) throw new Error(`SF ${sfMatchNumber} participant2 already filled`);

    await updatePlayoffMatch(sfMatch.id, { participant2Id: winnerId });
    return;
  }

  // Semi-Finals: Winner → Preliminary Finals
  if (match.round === "Semi-Finals") {
    // SF Match 1 winner → PF2 participant2
    // SF Match 2 winner → PF1 participant2
    const pfMatchNumber = match.matchNumber === 1 ? 2 : 1;
    const pfMatches = await findPlayoffMatchesByEventIdAndRound(eventId, "Preliminary Finals");
    const pfMatch = pfMatches.find((m) => m.matchNumber === pfMatchNumber);

    if (!pfMatch) throw new Error(`Preliminary Finals match ${pfMatchNumber} not found`);
    if (pfMatch.participant2Id) throw new Error(`PF ${pfMatchNumber} participant2 already filled`);

    await updatePlayoffMatch(pfMatch.id, { participant2Id: winnerId });
    return;
  }

  // Preliminary Finals: Winner → Grand Final
  if (match.round === "Preliminary Finals") {
    const gfMatches = await findPlayoffMatchesByEventIdAndRound(eventId, "Grand Final");
    const gfMatch = gfMatches[0];

    if (!gfMatch) throw new Error("Grand Final match not found");

    const participantSlot: 'participant1Id' | 'participant2Id' =
      match.matchNumber === 1 ? 'participant1Id' : 'participant2Id';

    if (gfMatch[participantSlot]) throw new Error(`GF ${participantSlot} already filled`);

    await updatePlayoffMatch(gfMatch.id, { [participantSlot]: winnerId });
    return;
  }

  // Grand Final: No advancement
  if (match.round === "Grand Final") {
    return;
  }
}

/**
 * Advance winner to next round using template-based logic
 * Works with any bracket template
 */
export async function advanceWinnerTemplate(
  matchId: string,
  winnerId: string,
  template: BracketTemplate
): Promise<void> {
  // Get the match
  const match = await findPlayoffMatchById(matchId);
  if (!match) throw new Error("Match not found");

  // Special handling for AFL 10 double-chance system
  // Phase 3.3: AFL has complex winner/loser advancement rules
  if (template.id === "afl_10") {
    const loserId = match.winnerId === match.participant1Id ? match.participant2Id : match.participant1Id;
    if (!loserId) throw new Error("Cannot determine loser for AFL advancement");
    return await advanceAFLWinner(match, winnerId, loserId);
  }

  // Special handling for NCAA 68 First Four
  // First Four winners advance to Round of 64 slots derived from the regions config
  if (template.id === "ncaa_68" && match.round === "First Four") {
    return await advanceFirstFourWinner(match, winnerId, template);
  }

  // Find current round in template
  const currentRoundIndex = template.rounds.findIndex(
    (r) => r.name === match.round
  );
  if (currentRoundIndex === -1) {
    throw new Error(`Round '${match.round}' not found in template`);
  }

  const currentRound = template.rounds[currentRoundIndex];

  // Check if there's a next round
  if (!currentRound.feedsInto) {
    // Championship game - no advancement needed
    return;
  }

  const nextRound = template.rounds.find((r) => r.name === currentRound.feedsInto);
  if (!nextRound) {
    throw new Error(`Next round '${currentRound.feedsInto}' not found in template`);
  }

  // Determine next match number and slot
  // This uses the same logic as before: matches advance in sequential pairs
  const nextMatchNumber = Math.ceil(match.matchNumber / 2);
  const participantSlot: 'participant1Id' | 'participant2Id' =
    match.matchNumber % 2 === 1 ? 'participant1Id' : 'participant2Id';

  // Find the next match
  const nextMatches = await findPlayoffMatchesByEventIdAndRound(
    match.scoringEventId,
    nextRound.name
  );
  const nextMatch = nextMatches.find((m) => m.matchNumber === nextMatchNumber);

  if (!nextMatch) {
    throw new Error(
      `Next match not found: round=${nextRound.name}, matchNumber=${nextMatchNumber}`
    );
  }

  // Check if the slot is already filled
  if (nextMatch[participantSlot]) {
    throw new Error(`Next match ${participantSlot} is already filled`);
  }

  // Fill the determined slot
  await updatePlayoffMatch(nextMatch.id, { [participantSlot]: winnerId });
}

/**
 * Advance a First Four winner to their target Round of 64 slot.
 *
 * The target match number is computed dynamically from the template's regions config:
 *   r64MatchNumber = regionIndex * 8 + matchIndexForSeedSlot(seedSlot) + 1
 *
 * The winner always fills participant2Id (they are the lower seed in their matchup).
 */
async function advanceFirstFourWinner(
  firstFourMatch: PlayoffMatch,
  winnerId: string,
  template: BracketTemplate
): Promise<void> {
  // Prefer the per-event stored region config (set at bracket generation time);
  // fall back to the template's built-in regions for backwards compatibility.
  const db = database();
  const eventRow = await db.query.scoringEvents.findFirst({
    where: eq(schema.scoringEvents.id, firstFourMatch.scoringEventId),
    columns: { bracketRegionConfig: true },
  });
  const regions =
    (eventRow?.bracketRegionConfig as BracketRegion[] | null) ?? template.regions;

  if (!regions) {
    throw new Error("NCAA 68 template requires regions config for First Four advancement");
  }

  const slotMap = buildNCAA68SlotMap(regions);
  const ffIndex = firstFourMatch.matchNumber - 1; // 0-indexed

  if (ffIndex < 0 || ffIndex >= slotMap.playInOffsets.length) {
    throw new Error(`Invalid First Four match number: ${firstFourMatch.matchNumber}`);
  }

  const { regionIndex, seedSlot } = slotMap.playInOffsets[ffIndex];
  const matchIndexInRegion = matchIndexForSeedSlot(seedSlot);
  // Each region contributes exactly 8 matches to the Round of 64, in region order
  const r64MatchNumber = regionIndex * 8 + matchIndexInRegion + 1;

  const roundOf64Matches = await findPlayoffMatchesByEventIdAndRound(
    firstFourMatch.scoringEventId,
    "Round of 64"
  );

  const targetMatch = roundOf64Matches.find((m) => m.matchNumber === r64MatchNumber);
  if (!targetMatch) {
    throw new Error(`Round of 64 match ${r64MatchNumber} not found`);
  }

  if (targetMatch.participant2Id) {
    throw new Error(`Round of 64 match ${r64MatchNumber} participant2Id is already filled`);
  }

  await updatePlayoffMatch(targetMatch.id, { participant2Id: winnerId });
}

/**
 * Generate FIFA 48 knockout bracket (empty, no participants assigned)
 * Creates 31 matches across 5 rounds identical to simple_32 structure.
 * Participants are assigned later via assignParticipantsToKnockout after group stage.
 */
async function generateFIFA48Bracket(
  eventId: string,
  template: BracketTemplate
): Promise<PlayoffMatch[]> {
  const matches: NewPlayoffMatch[] = [];

  for (const round of template.rounds) {
    for (let i = 0; i < round.matchCount; i++) {
      matches.push({
        scoringEventId: eventId,
        round: round.name,
        matchNumber: i + 1,
        participant1Id: null,
        participant2Id: null,
        isComplete: false,
        isScoring: round.isScoring,
        templateRound: round.name,
        seedInfo: null,
      });
    }
  }

  return await createManyPlayoffMatches(matches);
}

/**
 * Assign participants to Round of 32 knockout matches
 * Used after group stage to populate the empty knockout bracket
 *
 * @param eventId - The scoring event ID
 * @param assignments - Array of { matchNumber, slot, participantId }
 */
export async function assignParticipantsToKnockout(
  eventId: string,
  assignments: Array<{
    matchNumber: number;
    slot: "participant1Id" | "participant2Id";
    participantId: string;
  }>
): Promise<void> {
  // Get Round of 32 matches
  const r32Matches = await findPlayoffMatchesByEventIdAndRound(eventId, "Round of 32");

  for (const assignment of assignments) {
    const match = r32Matches.find((m) => m.matchNumber === assignment.matchNumber);
    if (!match) {
      throw new Error(`Round of 32 match ${assignment.matchNumber} not found`);
    }

    if (match[assignment.slot]) {
      throw new Error(
        `Round of 32 match ${assignment.matchNumber} ${assignment.slot} is already filled`
      );
    }

    await updatePlayoffMatch(match.id, {
      [assignment.slot]: assignment.participantId,
    });
  }
}
