import { createContext, useCallback, useEffect, useRef, useState, type ReactNode } from 'react';
import { RefreshCw, TriangleAlert } from 'lucide-react';
import { Button, Card, Spinner } from './components';
import {
  clearLegacyStoredApiKey,
  getAuthConfig,
  getAuthSession,
  type AuthConfig,
  type AuthSession
} from './api';
import { LoginScreen } from './screens/LoginScreen';
import { SetupScreen } from './screens/SetupScreen';

// The boot gate (#295). It wraps <App/> OUTSIDE the shell/router, so Login and Setup mint no URLs and a
// deep link survives an intervening sign-in. On mount it asks the PUBLIC `/api/v1/auth/config`, then (unless
// setup is required) `/api/v1/auth/session`, and renders Setup / Login / the app accordingly. A config-fetch
// failure lands on an explicit error state with Retry — a keyless, sessionless browser must ALWAYS be able
// to reach the login form, never a blank screen.

export interface AuthContextValue {
  username: string | null;
  method: string | null;
  /** Return to the login screen after the caller has POSTed `/api/v1/auth/logout`. */
  signOut: () => void;
  /** Flip the gate to the login screen (e.g. the 401 banner's "Sign in"). */
  requireLogin: () => void;
}

// A safe default so consumers (App, UserMenu, UnauthorizedBanner) render without a provider — e.g.
// `App.test.tsx` renders <App/> directly. AuthGate publishes the real value only in the `ready` state.
// Colocated with the gate that owns it (per the #295 plan); the fast-refresh rule is disabled to allow
// this non-component export alongside AuthGate, matching LocalLibraryEditScreen's established pattern.
// eslint-disable-next-line react-refresh/only-export-components
export const AuthContext = createContext<AuthContextValue>({
  username: null,
  method: null,
  signOut: () => {},
  requireLogin: () => {}
});

type GateState =
  | { status: 'checking' }
  | { status: 'error' }
  | { status: 'setup'; config: AuthConfig }
  | { status: 'login'; config: AuthConfig }
  | { status: 'ready'; session: AuthSession };

export function AuthGate({ children }: { children: ReactNode }) {
  const [state, setState] = useState<GateState>({ status: 'checking' });
  const activeRef = useRef(true);
  const seqRef = useRef(0);
  const configRef = useRef<AuthConfig | null>(null);
  const clearedRef = useRef(false);

  useEffect(() => {
    // Mount/unmount tracker — guards against the StrictMode double-mount and a slow fetch resolving
    // after unmount (spa-conventions §3). Re-arm on (re)mount so the second StrictMode pass is live.
    activeRef.current = true;
    return () => {
      activeRef.current = false;
    };
  }, []);

  // Runs the boot fetch, setting state ONLY in the async completions (never synchronously) so it's safe
  // to invoke from the mount effect without tripping the react-hooks "no set-state-in-effect" rule (§3):
  // the initial `checking` state is the useState default, so the effect path needs no synchronous reset.
  const runGate = useCallback(() => {
    const seq = ++seqRef.current;

    getAuthConfig()
      .then((config) => {
        if (!activeRef.current || seq !== seqRef.current) {
          return undefined;
        }

        configRef.current = config;

        if (config.setupRequired) {
          setState({ status: 'setup', config });
          return undefined;
        }

        return getAuthSession().then((session) => {
          if (!activeRef.current || seq !== seqRef.current) {
            return;
          }

          if (session.authenticated) {
            setState({ status: 'ready', session });
          } else {
            setState({ status: 'login', config });
          }
        });
      })
      .catch(() => {
        if (!activeRef.current || seq !== seqRef.current) {
          return;
        }
        setState({ status: 'error' });
      });
  }, []);

  useEffect(() => {
    runGate();
  }, [runGate]);

  // Re-run the gate from an event handler (Retry / recovery), resetting to the checking state first.
  const restart = useCallback(() => {
    setState({ status: 'checking' });
    runGate();
  }, [runGate]);

  useEffect(() => {
    // Reaching the app once means any pre-session `ctv-api-key` is now dead — clear it a single time.
    if (state.status === 'ready' && !clearedRef.current) {
      clearedRef.current = true;
      clearLegacyStoredApiKey();
    }
  }, [state.status]);

  const onAuthenticated = useCallback((session: AuthSession) => {
    setState({ status: 'ready', session });
  }, []);

  const requireLogin = useCallback(() => {
    const config = configRef.current;
    if (config) {
      setState({ status: 'login', config });
    } else {
      // No config cached (shouldn't happen post-boot) — re-run the whole gate to recover safely.
      restart();
    }
  }, [restart]);

  if (state.status === 'checking') {
    return (
      <div className="ctv-auth-page" role="status" aria-label="Checking sign-in status">
        <Spinner size={28} />
      </div>
    );
  }

  if (state.status === 'error') {
    return (
      <div className="ctv-auth-page">
        <Card padded style={{ maxWidth: 420, width: '100%' }} title="Can't reach the server">
          <div className="ctv-settings-warn-callout" role="alert">
            <TriangleAlert aria-hidden="true" color="var(--status-warn)" size={15} />
            <span>Couldn't load the sign-in configuration. Check that the server is running, then retry.</span>
          </div>
          <div style={{ marginTop: 16 }}>
            <Button onClick={restart} startIcon={<RefreshCw aria-hidden="true" size={14} />} variant="primary">
              Retry
            </Button>
          </div>
        </Card>
      </div>
    );
  }

  if (state.status === 'setup') {
    return <SetupScreen onAuthenticated={onAuthenticated} />;
  }

  if (state.status === 'login') {
    return <LoginScreen config={state.config} onAuthenticated={onAuthenticated} />;
  }

  return (
    <AuthContext.Provider
      value={{
        username: state.session.username ?? null,
        method: state.session.method ?? null,
        signOut: requireLogin,
        requireLogin
      }}
    >
      {children}
    </AuthContext.Provider>
  );
}
