import { auth } from "~/lib/auth.server";
import { addDays, subDays } from "date-fns";
import { toEventSortKey } from "~/lib/date-utils";
import {
  findLeagueById,
  findTeamsBySeasonId,
  findCommissionersByLeagueId,
  isUserLeagueMember,
  isCommissioner,
  findUsersByIds,
  getUserDisplayName,
  findDraftSlotsBySeasonId,
} from "~/models";
import { findCurrentSeasonWithSports } from "~/models/season";
import { getSeasonStandings } from "~/models/standings";
import { getUpcomingEventsForDraftedParticipants } from "~/models/scoring-event";
import { getUpcomingGroupStageMatchesForParticipants } from "~/models/group-stage-match";
import { getDraftedParticipantsBySportsSeason, getDraftedParticipantsWithPoints, type DraftedParticipantWithPoints } from "~/models/draft-pick";
import { getAuditLogForSeason } from "~/models/audit-log";
import { findUserById } from "~/models/user";
import { resolveUserAvatarData } from "~/lib/avatar-data";
import type { Route } from "./+types/$leagueId";

export async function loader(args: Route.LoaderArgs) {
  const session = await auth.api.getSession({ headers: args.request.headers });
  const userId = session?.user.id ?? null;
  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 findUsersByIds(allUserIds);
  const userById = new Map(userRows.map((u) => [u.id, u]));

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

  const ownerAvatarDataByUserId = Object.fromEntries(
    userRows.map((u) => [u.id, resolveUserAvatarData(u)])
  );

  const commissionerMap = new Map(
    commissionerIds
      .map((id) => [id, userById.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 dateFromStr = subDays(today, 1).toISOString().split("T")[0];
  const dateToStr = addDays(today, 30).toISOString().split("T")[0];

  const [participantsBySportsSeason, participantsWithPoints]: [
    Map<string, Array<{ id: string; name: string }>>,
    Map<string, DraftedParticipantWithPoints[]>
  ] = myTeam && season
    ? await Promise.all([
        getDraftedParticipantsBySportsSeason(myTeam.id, season.id),
        getDraftedParticipantsWithPoints(myTeam.id, season.id),
      ])
    : [new Map(), new Map()];

  const dateFrom = new Date(dateFromStr + "T00:00:00.000Z");
  const dateTo = new Date(dateToStr + "T23:59:59.999Z");

  const sportsSeasons = await Promise.all(
    rawSportsSeasons.map(async (ss) => {
      const draftedParticipants = participantsBySportsSeason.get(ss.id) ?? [];
      const draftedParticipantsWithPoints = participantsWithPoints.get(ss.id) ?? [];
      const upcomingParticipantEvents =
        draftedParticipants.length > 0
          ? await getUpcomingEventsForDraftedParticipants(
              ss.id,
              ss.scoringPattern ?? "",
              draftedParticipants,
              dateFromStr,
              dateToStr
            )
          : [];

      // For group-stage bracket sports, also include scheduled group matches
      if (ss.scoringPattern === "playoff_bracket" && draftedParticipants.length > 0) {
        const draftedIds = draftedParticipants.map((p) => p.id);
        const groupMatches = await getUpcomingGroupStageMatchesForParticipants(
          ss.id,
          draftedIds,
          dateFrom,
          dateTo
        );
        for (const gm of groupMatches) {
          const relevant = draftedParticipants.filter(
            (p) => p.id === gm.participant1.id || p.id === gm.participant2.id
          );
          upcomingParticipantEvents.push({
            id: gm.matchId,
            name: `${gm.participant1.name} vs ${gm.participant2.name}`,
            eventDate: gm.scheduledAt ? gm.scheduledAt.split("T")[0] : null,
            earliestGameTime: gm.scheduledAt || null,
            matchLabel: `Group ${gm.groupName} · MD${gm.matchday}`,
            eventType: "group_match",
            sportsSeasonId: ss.id,
            relevantParticipants: relevant,
          });
        }
      }

      return {
        id: ss.id,
        name: ss.name,
        status: ss.status as "upcoming" | "active" | "completed",
        scoringPattern: ss.scoringPattern,
        sport: ss.sport,
        upcomingParticipantEvents,
        draftedParticipantsWithPoints,
      };
    })
  );
  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}`,
        leagueId,
        leagueName: league.name,
      }))
    )
    .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;

  // Fetch recent audit log entries for the "Recent Activity" summary widget
  const recentActivity = season
    ? await getAuditLogForSeason(season.id, { limit: 5 })
    : { entries: [], total: 0, hasMore: false };

  const currentUser = userId ? await findUserById(userId) : null;

  // Show timezone banner in per_user overnight pause mode when user hasn't set their timezone
  const showTimezoneBanner =
    userId &&
    season?.status === "pre_draft" &&
    season?.overnightPauseMode === "per_user"
      ? !currentUser?.timezone
      : false;

  return {
    league,
    season,
    showTimezoneBanner: !!showTimezoneBanner,
    teams,
    commissioners,
    currentUserId: userId,
    currentUserTimezone: currentUser?.timezone ?? null,
    isUserCommissioner,
    ownerMap: Object.fromEntries(ownerMap),
    ownerAvatarDataByUserId,
    commissionerMap: Object.fromEntries(commissionerMap),
    availableTeamCount,
    sportsCount,
    teamsWithOwners,
    isDraftOrderSet,
    draftSlots,
    sportsSeasons,
    standings,
    origin,
    upcomingCalendarEvents,
    recentActivity,
  };
}
