import {
  ReactNode,
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useRef,
  useState,
} from "react";
import { useLocation, useNavigate } from "react-router";
import { apiClient, configureApiAuth } from "../lib/api";
import { UserDto } from "@sigma/common";

const STORAGE_KEY = "sigma.auth";

type LoginDto = {
  username: string;
  password: string;
};

type RegisterDto = {
  username: string;
  password: string;
};

type AuthContextValue = {
  user: UserDto | null;
  token: string | null;
  isAuthenticated: boolean;
  loading: boolean;
  login: (payload: LoginDto) => Promise<void>;
  register: (payload: RegisterDto) => Promise<void>;
  logout: () => void;
};

type SavedState = Pick<AuthContextValue, "user" | "token">;

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

function readStoredAuth() {
  try {
    const json = localStorage.getItem(STORAGE_KEY);
    if (!json) return null;

    return JSON.parse(json) as SavedState;
  } catch (error) {
    console.warn("Failed to parse stored auth", error);
    localStorage.removeItem(STORAGE_KEY);

    return null;
  }
}

export function AuthProvider({ children }: { children: ReactNode }) {
  const navigate = useNavigate();
  const location = useLocation();
  const [user, setUser] = useState<UserDto | null>(
    () => readStoredAuth()?.user ?? null
  );
  const [token, setToken] = useState<string | null>(
    () => readStoredAuth()?.token ?? null
  );
  const tokenRef = useRef(readStoredAuth()?.token ?? null);
  const logoutRef = useRef<(() => void) | null>(null);

  const [loading, setLoading] = useState(false);

  const persistAuth = useCallback((newUser: UserDto, newToken: string) => {
    setUser(newUser);
    setToken(newToken);
    tokenRef.current = newToken;

    localStorage.setItem(
      STORAGE_KEY,
      JSON.stringify({ user: newUser, token: newToken })
    );
  }, []);

  const clearAuth = useCallback(() => {
    setUser(null);
    setToken(null);
    tokenRef.current = null;

    localStorage.removeItem(STORAGE_KEY);
  }, []);

  const fetchProfile = useCallback(
    async ({ tokenOverride }: { tokenOverride?: string } = {}) => {
      const activeToken = tokenOverride ?? token;
      if (!activeToken) {
        throw new Error("Missing auth token for profile request");
      }

      const headers =
        tokenOverride !== undefined
          ? { Authorization: `Bearer ${activeToken}` }
          : undefined;

      const { data } = await apiClient.get<UserDto>("/users/me", {
        headers,
      });

      persistAuth(data, activeToken);

      return data;
    },
    [persistAuth, token]
  );

  const logout = useCallback(() => {
    clearAuth();

    navigate("/login", {
      replace: true,
      state: { from: location.pathname },
    });
  }, [clearAuth, navigate, location.pathname]);

  logoutRef.current = logout;

  configureApiAuth({
    tokenProvider: () => tokenRef.current,
    onUnauthorized: () => logoutRef.current?.(),
  });

  const authenticate = useCallback(
    async ({ username, password }: LoginDto) => {
      const { data } = await apiClient.post<{ auth_token?: string }>(
        "/auth/login",
        {
          username,
          password,
        }
      );

      const newToken = data?.auth_token;
      if (!newToken) {
        throw new Error("Login response missing auth token");
      }

      await fetchProfile({ tokenOverride: newToken });
    },
    [fetchProfile]
  );

  const login = useCallback(
    async (payload: LoginDto) => {
      setLoading(true);

      try {
        await authenticate(payload);

        navigate("/", { replace: true });
      } finally {
        setLoading(false);
      }
    },
    [authenticate, navigate]
  );

  const register = useCallback(
    async (payload: RegisterDto) => {
      setLoading(true);

      try {
        await apiClient.post("/users", payload);
        await authenticate(payload);

        navigate("/", { replace: true });
      } finally {
        setLoading(false);
      }
    },
    [authenticate, navigate]
  );

  useEffect(() => {
    if (!token || user) {
      return;
    }

    let cancelled = false;
    setLoading(true);

    fetchProfile()
      .catch(() => {
        if (!cancelled) {
          logout();
        }
      })
      .finally(() => {
        if (!cancelled) {
          setLoading(false);
        }
      });

    return () => {
      cancelled = true;
    };
  }, [token, user, fetchProfile, logout]);

  const value = useMemo<AuthContextValue>(
    () => ({
      user,
      token,
      isAuthenticated: Boolean(user && token),
      loading,
      login,
      register,
      logout,
    }),
    [user, token, loading, login, register, logout]
  );

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

export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error("useAuth must be used within AuthProvider");
  }

  return context;
}

export function useRequireAuth() {
  const context = useAuth();
  if (!context.isAuthenticated) {
    throw new Error("User is not authenticated");
  }

  return context;
}
