import { getAuth } from "@clerk/react-router/server";
import { addDays } from "date-fns";
import { toEventSortKey } from "~/lib/date-utils";
import {
  findLeagueById,
  findTeamsBySeasonId,
  findCommissionersByLeagueId,
  isUserLeagueMember,
  isCommissioner,
  findUsersByClerkIds,
  getUserDisplayName,
  findDraftSlotsBySeasonId,
} from "~/models";
import { findCurrentSeasonWithSports } from "~/models/season";
import { getSeasonStandings } from "~/models/standings";
import { getUpcomingEventsForDraftedParticipants } from "~/models/scoring-event";
import { getDraftedParticipantsBySportsSeason } from "~/models/draft-pick";
import type { Route } from "./+types/$leagueId";

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

  // Fetch league
  const league = await findLeagueById(leagueId);

  if (!league) {
    throw new Response("League not found", { status: 404 });
  }

  // Fetch current season with sports
  const seasonWithSports = await findCurrentSeasonWithSports(leagueId);
  const season = seasonWithSports || null;

  // Fetch commissioners
  const commissioners = await findCommissionersByLeagueId(leagueId);

  // Check if current user is a commissioner
  const isUserCommissioner = userId
    ? await isCommissioner(leagueId, userId)
    : false;

  // Check if user is a member (has a team in current season)
  const isUserMember = userId
    ? await isUserLeagueMember(leagueId, userId)
    : false;

  // Check access: user must be a commissioner, a member, or an admin
  // If not logged in or not authorized, throw 403
  if (!userId) {
    throw new Response("You must be logged in to view this league", {
      status: 401,
    });
  }

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

  // Fetch teams for current season
  const teams = season ? await findTeamsBySeasonId(season.id) : [];

  // Fetch draft slots if season is in pre_draft or draft status
  const draftSlots =
    season && (season.status === "pre_draft" || season.status === "draft")
      ? await findDraftSlotsBySeasonId(season.id)
      : [];

  // Fetch standings for active/completed seasons
  const standings =
    season && (season.status === "active" || season.status === "completed")
      ? await getSeasonStandings(season.id)
      : [];

  // Batch-fetch all users needed for owner and commissioner maps in one query
  const ownerIds = [...new Set(teams.map((t) => t.ownerId).filter((id): id is string => id !== null))];
  const commissionerIds = commissioners.map((c) => c.userId);
  const allUserIds = [...new Set([...ownerIds, ...commissionerIds])];
  const userRows = await findUsersByClerkIds(allUserIds);
  const userByClerkId = new Map(userRows.map((u) => [u.clerkId, u]));

  const ownerMap = new Map(
    ownerIds
      .map((id) => [id, userByClerkId.get(id)] as const)
      .filter((entry): entry is [string, NonNullable<typeof entry[1]>] => entry[1] !== undefined)
      .map(([id, user]) => [id, getUserDisplayName(user)])
  );

  const commissionerMap = new Map(
    commissionerIds
      .map((id) => [id, userByClerkId.get(id)] as const)
      .filter((entry): entry is [string, NonNullable<typeof entry[1]>] => entry[1] !== undefined)
      .map(([id, user]) => [id, getUserDisplayName(user)])
  );

  // Count available teams
  const availableTeamCount = teams.filter((t) => !t.ownerId).length;

  // Count teams with owners
  const teamsWithOwners = teams.filter((t) => t.ownerId !== null).length;

  // Get sports seasons data with upcoming participant events for the current user
  const rawSportsSeasons = seasonWithSports?.seasonSports?.map((ss) => ss.sportsSeason) || [];

  const myTeam = teams.find((t) => t.ownerId === userId) ?? null;
  const today = new Date();
  const calendarDateFrom = today;
  const calendarDateTo = addDays(today, 30);

  const participantsBySportsSeason = myTeam && season
    ? await getDraftedParticipantsBySportsSeason(myTeam.id, season.id)
    : new Map<string, Array<{ id: string; name: string }>>();

  const sportsSeasons = await Promise.all(
    rawSportsSeasons.map(async (ss) => {
      const draftedParticipants = participantsBySportsSeason.get(ss.id) ?? [];
      const upcomingParticipantEvents =
        draftedParticipants.length > 0
          ? await getUpcomingEventsForDraftedParticipants(
              ss.id,
              ss.scoringPattern ?? "",
              draftedParticipants,
              calendarDateFrom,
              calendarDateTo
            )
          : [];
      return {
        id: ss.id,
        name: ss.name,
        status: ss.status as "upcoming" | "active" | "completed",
        scoringPattern: ss.scoringPattern,
        sport: ss.sport,
        upcomingParticipantEvents,
      };
    })
  );
  const sportsCount = sportsSeasons.length;

  // Flatten all events into a panel-ready list, sorted by date
  const upcomingCalendarEvents = sportsSeasons
    .flatMap((ss) =>
      ss.upcomingParticipantEvents.map((e) => ({
        ...e,
        sportName: ss.sport.name,
        sportSeasonName: ss.name,
        sportsSeasonPageUrl: `/leagues/${leagueId}/sports-seasons/${ss.id}`,
      }))
    )
    .toSorted((a, b) => toEventSortKey(a).localeCompare(toEventSortKey(b)));

  // Check if draft order is set
  const isDraftOrderSet = draftSlots.length > 0;

  // Extract origin for client use (avoids SSR/client mismatch on invite URLs)
  const origin = new URL(args.request.url).origin;

  return {
    league,
    season,
    teams,
    commissioners,
    currentUserId: userId,
    isUserCommissioner,
    ownerMap: Object.fromEntries(ownerMap),
    commissionerMap: Object.fromEntries(commissionerMap),
    availableTeamCount,
    sportsCount,
    teamsWithOwners,
    isDraftOrderSet,
    draftSlots,
    sportsSeasons,
    standings,
    origin,
    upcomingCalendarEvents,
  };
}
