import { getAuth } from "@clerk/react-router/server";
import type { Route } from "./+types/$leagueId.sports-seasons.$sportsSeasonId";
import {
  findLeagueById,
  isUserLeagueMember,
  isCommissioner,
  findTeamsBySeasonId,
  getUserDisplayName,
} from "~/models";
import { getDraftPicks } from "~/models/draft-pick";
import { getSeasonResults } from "~/models/participant-season-result";
import { calculateBracketPoints } from "~/models/scoring-rules";
import { getQPStandings } from "~/models/qualifying-points";
import {
  getUpcomingScoringEvents,
  getRecentCompletedEvents,
} from "~/models/scoring-event";
import { getRegularSeasonStandings } from "~/models/regular-season-standings";
import { getAllParticipantEVsForSeason } from "~/models/participant-expected-value";
import { getBracketTemplate, getOrderedRoundsFromMatches } from "~/lib/bracket-templates";
import type { PlayoffMatch } from "~/models/playoff-match";
import { database } from "~/database/context";
import * as schema from "~/database/schema";
import { eq, and, inArray } from "drizzle-orm";

export async function loader(args: Route.LoaderArgs) {
  const { userId } = await getAuth(args);
  const { params } = args;
  const { leagueId, sportsSeasonId } = params;

  // Check authentication
  if (!userId) {
    throw new Response("You must be logged in to view this page", {
      status: 401,
    });
  }

  // Fetch league
  const league = await findLeagueById(leagueId);
  if (!league) {
    throw new Response("League not found", { status: 404 });
  }

  // Check access: user must be a commissioner or member
  const isUserCommissioner = await isCommissioner(leagueId, userId);
  const isUserMember = await isUserLeagueMember(leagueId, userId);

  if (!isUserCommissioner && !isUserMember) {
    throw new Response("You do not have access to this league", {
      status: 403,
    });
  }

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

  if (!sportsSeason) {
    throw new Response("Sports season not found", { status: 404 });
  }

  // Get the fantasy season for this league (to get teams and draft picks)
  // We need to find which season in this league includes this sports season
  // Filter by leagueId since a sports season can be linked to multiple leagues
  const seasonSport = sportsSeason.seasonSports?.find(
    (ss) => ss.season.leagueId === leagueId
  );
  if (!seasonSport) {
    throw new Response("This sports season is not linked to this league", {
      status: 404,
    });
  }

  const season = seasonSport.season;

  // Fetch teams and draft picks to determine ownership
  const teams = await findTeamsBySeasonId(season.id);
  const draftPicks = await getDraftPicks(season.id);

  // Build ownership map: participantId -> { teamName, teamId, ownerName }
  const ownershipMap = new Map<
    string,
    { teamName: string; teamId: string; ownerName?: string }
  >();

  // Get unique owner IDs and batch-fetch their user records in a single query
  const ownerIds = [...new Set(teams.map((t) => t.ownerId).filter(Boolean))] as string[];
  const ownerUserRows = ownerIds.length > 0
    ? await db.query.users.findMany({
        where: inArray(schema.users.clerkId, ownerIds),
        columns: { clerkId: true, username: true, displayName: true },
      })
    : [];
  const ownerMap = new Map(
    ownerUserRows.map((u) => [u.clerkId, getUserDisplayName(u) ?? "Unknown"])
  );

  // Map draft picks to ownership
  for (const pick of draftPicks) {
    const team = teams.find((t) => t.id === pick.teamId);
    if (team && pick.participantId) {
      ownershipMap.set(pick.participantId, {
        teamName: team.name,
        teamId: team.id,
        ownerName: team.ownerId ? ownerMap.get(team.ownerId) : undefined,
      });
    }
  }

  // Convert to array for serialization
  const teamOwnerships = Array.from(ownershipMap.entries()).map(
    ([participantId, data]) => ({
      participantId,
      ...data,
    })
  );

  // Derive which participant IDs the current user has drafted
  const userTeams = teams.filter((t) => t.ownerId === userId);
  const userTeamIds = new Set(userTeams.map((t) => t.id));
  const userParticipantIds = draftPicks
    .filter((p) => p.teamId && userTeamIds.has(p.teamId) && p.participantId)
    .map((p) => p.participantId as string);

  // Fetch pattern-specific data based on scoring pattern
  const scoringPattern = sportsSeason.scoringPattern;

  type SeasonStanding = { id: string; position: number; championshipPoints: string; participant: { id: string; name: string } };

  type PlayoffMatchWithRelations = PlayoffMatch & {
    participant1: { id: string; name: string } | null;
    participant2: { id: string; name: string } | null;
    winner: { id: string; name: string } | null;
    loser: { id: string; name: string } | null;
  };
  let playoffMatches: PlayoffMatchWithRelations[] = [];
  let playoffRounds: string[] = [];
  let preEliminatedParticipants: { id: string; name: string }[] = [];
  let participantPoints: { participantId: string; points: number }[] = [];
  let partialScoreParticipantIds: string[] = [];
  let seasonStandings: SeasonStanding[] = [];
  type QPStanding = Awaited<ReturnType<typeof getQPStandings>>[number];
  let qpStandings: QPStanding[] = [];

  if (scoringPattern === "playoff_bracket") {
    // Fetch playoff matches via scoring events
    const events = await db.query.scoringEvents.findMany({
      where: eq(schema.scoringEvents.sportsSeasonId, sportsSeasonId),
    });

    let templateId: string | undefined;

    if (events.length > 0) {
      const eventIds = events.map((e) => e.id);
      const matches = await db.query.playoffMatches.findMany({
        where: (pm) => inArray(pm.scoringEventId, eventIds),
        with: {
          participant1: true,
          participant2: true,
          winner: true,
          loser: true,
        },
      });
      playoffMatches = matches as PlayoffMatchWithRelations[];

      templateId = events.find((e) => e.bracketTemplateId)?.bracketTemplateId ?? undefined;
      const template = templateId ? getBracketTemplate(templateId) : undefined;
      playoffRounds = getOrderedRoundsFromMatches(matches, template);
    }

    // Fetch group-stage losers and all participant results for bracket scoring
    const [eliminatedResults, allResults] = await Promise.all([
      db.query.participantResults.findMany({
        where: and(
          eq(schema.participantResults.sportsSeasonId, sportsSeasonId),
          eq(schema.participantResults.finalPosition, 0)
        ),
        with: { participant: true },
      }),
      db.query.participantResults.findMany({
        where: eq(schema.participantResults.sportsSeasonId, sportsSeasonId),
      }),
    ]);

    preEliminatedParticipants = eliminatedResults
      .filter((r): r is typeof r & { participant: NonNullable<typeof r.participant> } => r.participant !== null && r.participant !== undefined)
      .map((r) => ({ id: r.participant.id, name: r.participant.name }));

    // Compute per-participant fantasy points directly from participant_results.
    // This covers both finalized placements and progressive floor scores for still-alive participants.
    const scoringRules = {
      pointsFor1st: season.pointsFor1st,
      pointsFor2nd: season.pointsFor2nd,
      pointsFor3rd: season.pointsFor3rd,
      pointsFor4th: season.pointsFor4th,
      pointsFor5th: season.pointsFor5th,
      pointsFor6th: season.pointsFor6th,
      pointsFor7th: season.pointsFor7th,
      pointsFor8th: season.pointsFor8th,
    };
    const seenParticipantIds = new Set<string>();
    participantPoints = allResults
      .filter((r) => r.finalPosition !== null && r.finalPosition > 0)
      .filter((r) => {
        if (seenParticipantIds.has(r.participantId)) return false;
        seenParticipantIds.add(r.participantId);
        return true;
      })
      .map((r) => ({
        participantId: r.participantId,
        points: calculateBracketPoints(r.finalPosition ?? 0, scoringRules, templateId),
      }));

    // Track which participants have provisional (floor) scores — still competing
    partialScoreParticipantIds = allResults
      .filter((r) => r.isPartialScore)
      .map((r) => r.participantId);
  } else if (scoringPattern === "season_standings") {
    // Fetch F1-style championship standings and map to SeasonStanding shape
    const results = await getSeasonResults(sportsSeasonId);
    seasonStandings = results
      .filter((r) => r.currentPosition !== null || (r.currentPoints && parseFloat(r.currentPoints) > 0))
      .map((r) => ({
        id: r.id,
        position: r.currentPosition ?? 999,
        championshipPoints: r.currentPoints ?? "0",
        participant: {
          id: r.participant.id,
          name: r.participant.name,
        },
      }));
  } else if (scoringPattern === "qualifying_points") {
    // Fetch qualifying points standings
    const standings = await getQPStandings(sportsSeasonId);
    qpStandings = standings;
  }

  // Fetch event schedule for all patterns (upcoming + recent)
  const [upcomingEvents, recentEvents] = await Promise.all([
    getUpcomingScoringEvents(sportsSeasonId, 5),
    getRecentCompletedEvents(sportsSeasonId, 3),
  ]);

  // Fetch regular season standings for team-sport playoff_bracket seasons (NBA, NHL)
  const isTeamBracket =
    scoringPattern === "playoff_bracket" && sportsSeason.sport?.type === "team";
  const [regularSeasonStandings, regularSeasonEvs] = isTeamBracket
    ? await Promise.all([
        getRegularSeasonStandings(sportsSeasonId),
        getAllParticipantEVsForSeason(sportsSeasonId),
      ])
    : [[], []];

  // Derive seasonIsFinalized from status
  const seasonIsFinalized = sportsSeason.status === "completed";

  // Build participantId → expectedValue map for display
  const participantEvs = Object.fromEntries(
    regularSeasonEvs.map((ev) => [ev.participantId, ev.expectedValue])
  );

  return {
    league,
    season,
    sportsSeason,
    scoringPattern,
    playoffMatches,
    playoffRounds,
    preEliminatedParticipants,
    participantPoints,
    partialScoreParticipantIds,
    seasonStandings,
    qpStandings,
    teamOwnerships,
    userParticipantIds,
    upcomingEvents,
    recentEvents,
    seasonIsFinalized,
    regularSeasonStandings,
    participantEvs,
  };
}
