import { useEffect, useState, type ReactNode } from 'react';
import { useDispatch } from 'react-redux';
import { PageLoader } from '@ema-platform/ui';
import { authStorage } from '../utils/auth-storage';
import { hydrateAuth, logout, setToken, setUser } from '../store/auth.slice';
import { refreshAccessToken } from '../utils/refresh-token';
import type { AuthUser } from '../types/auth.types';

import { BASE_API_URL } from '@ema-platform/api';

/**
 * Restores the signed-in session before the router renders.
 *
 * Redux starts empty on every page load while the token lives in
 * localStorage, so without this a refresh leaves the app half–signed-in: the
 * route guards see a token and let you through, but pages that read
 * `auth.user` think you are a stranger.
 *
 * A token the server no longer accepts is cleared here rather than left to
 * strand the user on a page they cannot act on or sign out of.
 */
export function AuthBootstrap({ children }: { children: ReactNode }) {
  const dispatch = useDispatch();
  const [ready, setReady] = useState(false);

  useEffect(() => {
    let cancelled = false;

    async function restore() {
      dispatch(hydrateAuth());

      const token = authStorage.getToken();
      const cachedUser = authStorage.getUser<AuthUser>();
      if (!token) {
        if (!cancelled) setReady(true);
        return;
      }

      try {
        let response = await fetch(`${BASE_API_URL}/auth/me`, {
          headers: { Authorization: `Bearer ${token}` },
        });

        // An expired access token is the normal state after a day away — spend
        // the refresh token before deciding the session is over. Without this
        // a lapsed token logs the user out on load even though the credential
        // to renew it is sitting right next to it in storage.
        if (response.status === 401 || response.status === 403) {
          try {
            const fresh = await refreshAccessToken();
            dispatch(setToken(fresh));
            response = await fetch(`${BASE_API_URL}/auth/me`, {
              headers: { Authorization: `Bearer ${fresh}` },
            });
          } catch (err) {
            // Only the server rejecting the refresh token ends the session —
            // same rule as the API layer's 401 handler. A 502 or a network
            // blip during refresh keeps the stored session; the screens
            // surface their own errors.
            if (!(err as { sessionExpired?: boolean })?.sessionExpired) return;
            // Rejected — fall through to the logout below.
          }
        }

        if (response.ok) {
          const user = (await response.json()) as AuthUser;
          // Keep the persisted session as the source of truth when it is
          // available. A successful profile update writes it immediately,
          // while `/auth/me` can briefly return a stale read and otherwise
          // undo that update on every page refresh. We still make this call
          // to validate the token and clear invalid sessions below.
          if (!cancelled && !cachedUser) dispatch(setUser(user));
        } else if (response.status === 401 || response.status === 403) {
          // Expired or revoked — drop it so the user gets a login screen
          // instead of a dead end.
          if (!cancelled) dispatch(logout());
        }
      } catch {
        // Offline or the API is down: keep the stored session and let the
        // individual screens surface their own errors.
      } finally {
        if (!cancelled) setReady(true);
      }
    }

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

  // Rendering the router before the session resolves would let the guards
  // redirect based on a state that is about to change.
  if (!ready) return <PageLoader label="Authenticating Maritime Session…" height="100vh" />;

  return <>{children}</>;
}
