import {
  createContext,
  type ReactNode,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
} from 'react';

import { login as loginRequest, register as registerRequest } from '../services/api';
import type { AuthenticatedUser } from '../types/auth';

const STORAGE_TOKEN_KEY = 'rail-game/auth/token';
const STORAGE_USER_KEY = 'rail-game/auth/user';

type AuthStatus = 'unauthenticated' | 'authenticating' | 'authenticated';

interface AuthContextValue {
  readonly token: string | null;
  readonly user: AuthenticatedUser | null;
  readonly status: AuthStatus;
  readonly error: string | null;
  login: (username: string, password: string) => Promise<boolean>;
  register: (
    username: string,
    password: string,
    fullName?: string | null
  ) => Promise<boolean>;
  logout: (reason?: string) => void;
}

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

function readFromStorage<T>(key: string): T | null {
  if (typeof window === 'undefined') {
    return null;
  }
  const raw = window.localStorage.getItem(key);
  if (!raw) {
    return null;
  }
  try {
    return JSON.parse(raw) as T;
  } catch {
    return null;
  }
}

function writeToStorage(key: string, value: unknown): void {
  if (typeof window === 'undefined') {
    return;
  }
  window.localStorage.setItem(key, JSON.stringify(value));
}

function removeFromStorage(key: string): void {
  if (typeof window === 'undefined') {
    return;
  }
  window.localStorage.removeItem(key);
}

interface AuthProviderProps {
  readonly children: ReactNode;
}

export function AuthProvider({ children }: AuthProviderProps): JSX.Element {
  const [token, setToken] = useState<string | null>(null);
  const [user, setUser] = useState<AuthenticatedUser | null>(null);
  const [status, setStatus] = useState<AuthStatus>('unauthenticated');
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const storedToken = readFromStorage<string>(STORAGE_TOKEN_KEY);
    const storedUser = readFromStorage<AuthenticatedUser>(STORAGE_USER_KEY);

    if (storedToken && storedUser) {
      setToken(storedToken);
      setUser(storedUser);
      setStatus('authenticated');
    }
  }, []);

  const logout = useCallback((reason?: string) => {
    setToken(null);
    setUser(null);
    setStatus('unauthenticated');
    setError(reason ?? null);
    removeFromStorage(STORAGE_TOKEN_KEY);
    removeFromStorage(STORAGE_USER_KEY);
  }, []);

  const login = useCallback(async (username: string, password: string) => {
    setStatus('authenticating');
    setError(null);
    try {
      const response = await loginRequest({ username, password });
      setToken(response.accessToken);
      setUser(response.user);
      setStatus('authenticated');
      writeToStorage(STORAGE_TOKEN_KEY, response.accessToken);
      writeToStorage(STORAGE_USER_KEY, response.user);
      return true;
    } catch (err) {
      setStatus('unauthenticated');
      const message = err instanceof Error ? err.message : 'Authentication failed';
      setError(message);
      return false;
    }
  }, []);

  const register = useCallback(
    async (username: string, password: string, fullName?: string | null) => {
      setStatus('authenticating');
      setError(null);
      try {
        const response = await registerRequest({ username, password, fullName });
        setToken(response.accessToken);
        setUser(response.user);
        setStatus('authenticated');
        writeToStorage(STORAGE_TOKEN_KEY, response.accessToken);
        writeToStorage(STORAGE_USER_KEY, response.user);
        return true;
      } catch (err) {
        setStatus('unauthenticated');
        const message = err instanceof Error ? err.message : 'Registration failed';
        setError(message);
        return false;
      }
    },
    []
  );

  const value = useMemo<AuthContextValue>(
    () => ({ token, user, status, error, login, register, logout }),
    [token, user, status, error, login, register, logout]
  );

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

export function useAuth(): AuthContextValue {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
}
