"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 { setToken, SESSION_EXPIRED_EVENT } from "@/services/http";
import { getMyProfile, logout } from "@/services/auth-api";
import { clearAdminSession, readCachedAdmin } from "@/services/auth";
import { SESSION_STORAGE_KEY } from "@/config/site";
import type { Admin } from "@/types/admin";

interface SessionValue {
  /** The signed-in back office admin, refreshed from the API once available. */
  admin: Admin | null;
  /** Optimistic cached admin shown while the API is loading. */
  cachedAdmin: Admin | null;
  /** Whether the session is being (re)validated right now. */
  loading: boolean;
  /** Whether a token exists at all. */
  authenticated: boolean;
  setAdmin: (admin: Admin) => void;
  signOut: (clientSideOnly?: boolean) => Promise<void>;
}

const SessionContext = createContext<SessionValue | null>(null);

export function SessionProvider({ children }: { children: ReactNode }) {
  const queryClient = useQueryClient();
  const router = useRouter();
  const [cachedAdmin, setCachedAdmin] = useState<Admin | null>(() => readCachedAdmin());
  const [admin, setAdminState] = useState<Admin | null>(null);
  const [loading, setLoading] = useState(true);

  // Read synchronously each render (not memoized): a successful login writes
  // the token and navigates to the dashboard within the same provider lifetime,
  // so the guard must see the freshly stored token — not a value frozen at mount.
  const hasToken =
    typeof window === "undefined" ? false : Boolean(window.localStorage.getItem(SESSION_STORAGE_KEY));

  useEffect(() => {
    if (!hasToken) return;

    let cancelled = false;
    (async () => {
      try {
        const response = await getMyProfile();
        if (cancelled) return;
        const fresh = response.Model;
        setAdminState(fresh);
        try {
          localStorage.setItem("kapitano.admin.profile", JSON.stringify(fresh));
        } catch {
          // ignore
        }
      } catch {
        // Session invalid — the HTTP layer already dispatched SESSION_EXPIRED_EVENT.
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();

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

  const handleSessionExpired = useCallback(() => {
    clearAdminSession();
    setAdminState(null);
    setCachedAdmin(null);
    queryClient.clear();
    const destination = window.location.pathname === "/" ? "" : `?next=${encodeURIComponent(window.location.pathname + window.location.search)}`;
    router.replace(`/${destination}`);
  }, [queryClient, router]);

  useEffect(() => {
    window.addEventListener(SESSION_EXPIRED_EVENT, handleSessionExpired);
    return () => window.removeEventListener(SESSION_EXPIRED_EVENT, handleSessionExpired);
  }, [handleSessionExpired]);

  const setAdmin = useCallback((next: Admin) => {
    setAdminState(next);
    try {
      localStorage.setItem("kapitano.admin.profile", JSON.stringify(next));
    } catch {
      // ignore
    }
  }, []);

  const signOut = useCallback(
    async (clientSideOnly = false) => {
      if (!clientSideOnly) {
        try {
          await logout();
        } catch {
          // proceed even if the server is unreachable
        }
      }
      clearAdminSession();
      setAdminState(null);
      setCachedAdmin(null);
      queryClient.clear();
      setToken(null);
      router.replace("/");
    },
    [queryClient, router],
  );

  const value = useMemo<SessionValue>(
    () => ({
      admin,
      cachedAdmin,
      // Without a token nothing is being validated — never block on loading.
      loading: hasToken ? loading : false,
      authenticated: Boolean(admin) || hasToken,
      setAdmin,
      signOut,
    }),
    [admin, cachedAdmin, loading, hasToken, setAdmin, signOut],
  );

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

export function useSession(): SessionValue {
  const context = useContext(SessionContext);
  if (!context) throw new Error("useSession must be used inside <SessionProvider>");
  return context;
}