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

import { fetchMe, loginWithEmailPassword, MeResponse, switchTenantRequest, Tenant } from '@/src/lib/api';
import { hydrateApiBaseUrl } from '@/src/lib/server-config';
import { clearStoredToken, getStoredToken, setStoredToken, tokenStorageInfo } from '@/src/lib/token-storage';

export type AuthUser = MeResponse['user'];

type AuthContextValue = {
  isBootstrapping: boolean;
  token: string | null;
  user: AuthUser | null;
  tenants: Tenant[];
  activeTenantId: number | null;
  activeTenant: Tenant | null;
  profile: Record<string, unknown> | null;
  permissions: string[];
  requiresTenantSelection: boolean;
  login: (email: string, password: string) => Promise<void>;
  logout: () => Promise<void>;
  refreshUser: () => Promise<void>;
  switchTenant: (tenantId: number) => Promise<void>;
};

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

function normalizeTenantId(value: number | string | null | undefined): number | null {
  if (value === null || value === undefined || value === '') {
    return null;
  }

  const parsed = Number(value);
  return Number.isNaN(parsed) ? null : parsed;
}

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [isBootstrapping, setIsBootstrapping] = useState(true);
  const [token, setToken] = useState<string | null>(null);
  const [user, setUser] = useState<AuthUser | null>(null);
  const [tenants, setTenants] = useState<Tenant[]>([]);
  const [activeTenantId, setActiveTenantId] = useState<number | null>(null);
  const [profile, setProfile] = useState<Record<string, unknown> | null>(null);
  const [permissions, setPermissions] = useState<string[]>([]);

  const resetSession = useCallback(() => {
    setUser(null);
    setTenants([]);
    setActiveTenantId(null);
    setProfile(null);
    setPermissions([]);
  }, []);

  const hydrateSession = useCallback(
    async (tokenToUse: string) => {
      const me = await fetchMe(tokenToUse);
      setUser(me.user);
      setTenants(me.tenants || []);
      setActiveTenantId(normalizeTenantId(me.activeTenant));
      setProfile(me.profile || null);
      setPermissions(me.permissions || []);
    },
    []
  );

  const logout = useCallback(async () => {
    await clearStoredToken();
    setToken(null);
    resetSession();
  }, [resetSession]);

  const refreshUser = useCallback(async () => {
    if (!token) {
      resetSession();
      return;
    }

    try {
      await hydrateSession(token);
    } catch {
      await logout();
    }
  }, [hydrateSession, logout, resetSession, token]);

  useEffect(() => {
    async function bootstrap() {
      await hydrateApiBaseUrl();
      const storedToken = await getStoredToken();

      if (!storedToken) {
        setIsBootstrapping(false);
        return;
      }

      try {
        await hydrateSession(storedToken);
        setToken(storedToken);
      } catch {
        await clearStoredToken();
        setToken(null);
        resetSession();
      } finally {
        setIsBootstrapping(false);
      }
    }

    void bootstrap();
  }, [hydrateSession, resetSession]);

  const login = useCallback(
    async (email: string, password: string) => {
      const nextToken = await loginWithEmailPassword(email, password);
      await setStoredToken(nextToken);
      await hydrateSession(nextToken);
      setToken(nextToken);
    },
    [hydrateSession]
  );

  const switchTenant = useCallback(
    async (tenantId: number) => {
      if (!token) {
        throw new Error('No active session found.');
      }

      const nextToken = await switchTenantRequest(tenantId, token);
      await setStoredToken(nextToken);
      await hydrateSession(nextToken);
      setToken(nextToken);
    },
    [token, hydrateSession]
  );

  const activeTenant = useMemo(
    () => tenants.find((tenant) => Number(tenant.id) === activeTenantId) || null,
    [activeTenantId, tenants]
  );

  const requiresTenantSelection = Boolean(token) && tenants.length > 0 && !activeTenantId;

  const value = useMemo(
    () => ({
      isBootstrapping,
      token,
      user,
      tenants,
      activeTenantId,
      activeTenant,
      profile,
      permissions,
      requiresTenantSelection,
      login,
      logout,
      refreshUser,
      switchTenant,
    }),
    [
      activeTenant,
      activeTenantId,
      isBootstrapping,
      login,
      logout,
      permissions,
      profile,
      refreshUser,
      requiresTenantSelection,
      switchTenant,
      tenants,
      token,
      user,
    ]
  );

  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 inside AuthProvider');
  }

  return context;
}

export function useTokenStorageInfo() {
  return tokenStorageInfo;
}
