import { redirect } from "@tanstack/react-router";
import UserRoles from "supertokens-node/recipe/userroles";
import UserMetadata from "supertokens-node/recipe/usermetadata";
import { getSessionForStart } from "@/lib/supertokens/recipes/start-session";
import { Logger } from "@/lib/logger";
import { pbAdmin } from "@/lib/pocketbase/client";
import { PUBLIC_ROUTES } from "./supertokens";
import type { Player } from "@/features/players/types";

const logger = new Logger("Middleware");

const ROLES_METADATA_TTL_MS = 60 * 1000;
const rolesMetadataCache = new Map<
  string,
  { roles: string[]; metadata: any; expiresAt: number }
>();

export const invalidateUserCache = (userAuthId: string) => {
  rolesMetadataCache.delete(userAuthId);
};

export const fetchUserRoles = async (userAuthId: string) => {
  const response = await UserRoles.getRolesForUser("public", userAuthId);
  return response;
};

const getRolesAndMetadata = async (userAuthId: string) => {
  const now = Date.now();
  const cached = rolesMetadataCache.get(userAuthId);
  if (cached && cached.expiresAt > now) {
    return cached;
  }

  const [{ roles }, { metadata }] = await Promise.all([
    fetchUserRoles(userAuthId),
    UserMetadata.getUserMetadata(userAuthId),
  ]);

  const entry = { roles, metadata, expiresAt: now + ROLES_METADATA_TTL_MS };
  rolesMetadataCache.set(userAuthId, entry);
  return entry;
};

export const updateUserMetadataFields = async (
  userAuthId: string,
  fields: Record<string, any>
) => {
  await UserMetadata.updateUserMetadata(userAuthId, fields);
  invalidateUserCache(userAuthId);
};

const verifySuperTokensSession = async (request: Request) => {
  let session = await getSessionForStart(request, { sessionRequired: false });

  if (session?.needsRefresh) {
    logger.info("Session needs refresh - redirecting to client");
    return { context: { session: { tryRefresh: true } } };
  }

  const userAuthId = session?.userId;

  if (!userAuthId || !session) {
    logger.info("No authenticated user");
    return { context: { userAuthId: null, roles: [] } };
  }

  const { roles, metadata } = await getRolesAndMetadata(userAuthId);

  return {
    context: {
      userAuthId,
      roles,
      metadata,
      phone: session.phone,
      session: {
        accessTokenPayload: session.accessTokenPayload,
        sessionHandle: session.sessionHandle,
      },
    },
  };
};

const LAST_ACTIVITY_THROTTLE_MS = 5 * 60 * 1000;
const lastActivityWriteAt = new Map<string, number>();

export const getSessionContextCore = async (
  request: Request,
  options?: { isServerFunction?: boolean }
) => {
  const session = await verifySuperTokensSession(request);

  if (session.context.session?.tryRefresh) {
    if (options?.isServerFunction) {
      throw new Error("SESSION_REFRESH_REQUIRED");
    }

    const url = new URL(request.url);

    if (PUBLIC_ROUTES.some((route) => url.pathname.startsWith(route))) {
      throw new Error("Unauthenticated");
    }

    const from = url.pathname + url.search;

    throw redirect({
      to: "/refresh-session",
      search: from === "/" ? {} : { redirect: from },
    });
  }

  if (!session.context.userAuthId) {
    logger.error("Unauthenticated user", session.context);
    throw new Error("Unauthenticated");
  }

  let player: Player | null = null;

  try {
    player = await pbAdmin.getPlayerByAuthId(session.context.userAuthId);
    if (player) {
      const now = Date.now();
      const lastWrite = lastActivityWriteAt.get(session.context.userAuthId) ?? 0;
      if (now - lastWrite > LAST_ACTIVITY_THROTTLE_MS) {
        lastActivityWriteAt.set(session.context.userAuthId, now);
        await pbAdmin.updatePlayer(player.id, {
          last_activity: new Date().toISOString(),
        });
      }
    }
  } catch (error) {
    logger.error("Failed to update player last_activity", error);
  }

  return {
    userAuthId: session.context.userAuthId,
    roles: session.context.roles,
    metadata: session.context.metadata,
    phone: session.context.phone,
    player,
  };
};
