import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';
import { clearTokens, setTokens, markRefreshFailure, setAccessToken } from './auth-state';
import { apiGet, ApiError } from './api';

export type Role = 'super_admin' | 'tenant_admin' | 'viewer';

export interface MeResponse {
  id: string;
  email: string;
  role: Role;
  tenant_id: string | null;
  tenant_slug: string | null;
  status: 'active' | 'pending' | 'disabled';
}

export interface LoginResponse {
  access_token: string;
  refresh_token: string;
  expires_in: number;
  user: MeResponse;
}

export interface AuthContextValue {
  user: MeResponse | null;
  loading: boolean;
  bootError: string | null;
  login: (email: string, password: string) => Promise<void>;
  logout: () => Promise<void>;
  refresh: () => Promise<void>;
}

const AuthContext = createContext<AuthContextValue | null>(null);

export function useAuth(): AuthContextValue {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth must be used inside <AuthProvider>');
  return ctx;
}

export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<MeResponse | null>(null);
  const [loading, setLoading] = useState(true);
  const [bootError, setBootError] = useState<string | null>(null);

  const fetchMe = useCallback(async (): Promise<MeResponse | null> => {
    try {
      return await apiGet<MeResponse>('/v1/auth/me');
    } catch (err) {
      if (err instanceof ApiError && err.status === 401) return null;
      throw err;
    }
  }, []);

  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const refreshRes = await fetch('/v1/auth/refresh', {
          method: 'POST',
          credentials: 'include',
        });
        if (refreshRes.ok) {
          const data = (await refreshRes.json()) as { access_token?: string; refresh_token?: string };
          if (data.access_token && data.refresh_token) {
            setTokens({ access: data.access_token, refresh: data.refresh_token });
          }
        }
        const me = await fetchMe();
        if (!cancelled) {
          setUser(me);
          setBootError(null);
        }
      } catch (err) {
        if (!cancelled) {
          setBootError(err instanceof Error ? err.message : 'unknown');
          setUser(null);
        }
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [fetchMe]);

  const login = useCallback(async (email: string, password: string) => {
    markRefreshFailure(window.location.pathname + window.location.search);
    const res = await fetch('/v1/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'include',
      body: JSON.stringify({ email, password }),
    });
    if (!res.ok) {
      const body = await res.json().catch(() => null);
      const msg = (body as { error?: string; message?: string } | null)?.message
        ?? `${res.status} ${res.statusText}`;
      throw new Error(msg);
    }
    const data = (await res.json()) as LoginResponse;
    setTokens({ access: data.access_token, refresh: data.refresh_token });
    setUser(data.user);
    setBootError(null);
  }, []);

  const logout = useCallback(async () => {
    try {
      await fetch('/v1/auth/logout', { method: 'POST', credentials: 'include' });
    } catch {
      // Even if the server logout fails, clear local state.
    }
    clearTokens();
    setUser(null);
  }, []);

  const refresh = useCallback(async () => {
    const me = await fetchMe();
    setUser(me);
  }, [fetchMe]);

  const value = useMemo<AuthContextValue>(
    () => ({ user, loading, bootError, login, logout, refresh }),
    [user, loading, bootError, login, logout, refresh],
  );

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

// Re-exported so external modules (api.ts, tests) can apply a
// refreshed access token in flight.
export { setAccessToken };
