"use client";

import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
  type ReactNode,
} from "react";
import { useRouter } from "next/navigation";
import { useQueryClient } from "@tanstack/react-query";
import { PORTAL_SESSION_EXPIRED_EVENT } from "@/config/site";
import {
  clearPortalSession,
  getPortalProfile,
  hasPortalToken,
  logoutStoreUser,
  readCachedPortalUser,
  storePortalSession,
} from "@/services/portal";
import { allowedPortalModules, type PortalModule } from "@/config/portal-modules";
import type { PortalUser } from "@/types/portal";

interface PortalSessionValue {
  /** The signed-in store user, refreshed from the API once available. */
  user: PortalUser | null;
  /** Whether the session is being (re)validated right now. */
  loading: boolean;
  /** Whether a token exists at all. */
  authenticated: boolean;
  /** The screens this user may open — the one list the menu, home and route guard all read. */
  modules: PortalModule[];
  signIn: (user: PortalUser, token: string) => void;
  signOut: () => Promise<void>;
}

const PortalSessionContext = createContext<PortalSessionValue | null>(null);

/**
 * The store portal's session, kept apart from the back office's `SessionProvider`.
 *
 * Mounted by the `/portal` layout only, so the back office never loads it, and it reads and writes
 * its own storage keys. A store user and an admin can be signed in in the same browser without
 * either seeing — or signing out — the other.
 */
export function PortalSessionProvider({ children }: { children: ReactNode }) {
  const queryClient = useQueryClient();
  const router = useRouter();
  const [user, setUser] = useState<PortalUser | null>(() => readCachedPortalUser());
  const [tokenPresent, setTokenPresent] = useState<boolean>(() => hasPortalToken());
  const [loading, setLoading] = useState<boolean>(() => hasPortalToken());

  // Revalidate on mount: the cached user may be a day old, and the store's status — which decides
  // what the menu shows — can have changed to approved or suspended since.
  useEffect(() => {
    if (!tokenPresent) return;

    let cancelled = false;
    (async () => {
      try {
        const response = await getPortalProfile();
        if (cancelled || !response.Model) return;
        setUser(response.Model);
        try {
          localStorage.setItem("kapitano.portal.profile", JSON.stringify(response.Model));
        } catch {
          // ignore
        }
      } catch {
        // Invalid session: the HTTP layer already dispatched the expired event.
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();

    return () => {
      cancelled = true;
    };
  }, [tokenPresent]);

  const reset = useCallback(() => {
    clearPortalSession();
    setUser(null);
    setTokenPresent(false);
    setLoading(false);
    queryClient.clear();
  }, [queryClient]);

  useEffect(() => {
    const onExpired = () => {
      reset();
      router.replace("/portal/login");
    };
    window.addEventListener(PORTAL_SESSION_EXPIRED_EVENT, onExpired);
    return () => window.removeEventListener(PORTAL_SESSION_EXPIRED_EVENT, onExpired);
  }, [reset, router]);

  const signIn = useCallback((next: PortalUser, token: string) => {
    storePortalSession(next, token);
    setUser(next);
    setTokenPresent(true);
    setLoading(false);
  }, []);

  const signOut = useCallback(async () => {
    try {
      await logoutStoreUser();
    } catch {
      // proceed even if the server is unreachable
    }
    reset();
    router.replace("/portal/login");
  }, [reset, router]);

  const value = useMemo<PortalSessionValue>(
    () => ({
      user,
      loading: tokenPresent ? loading : false,
      authenticated: tokenPresent,
      modules: allowedPortalModules(user),
      signIn,
      signOut,
    }),
    [user, loading, tokenPresent, signIn, signOut],
  );

  return <PortalSessionContext.Provider value={value}>{children}</PortalSessionContext.Provider>;
}

export function usePortalSession(): PortalSessionValue {
  const context = useContext(PortalSessionContext);
  if (!context) throw new Error("usePortalSession must be used inside <PortalSessionProvider>");
  return context;
}
