import { createContext, useCallback, useContext, useEffect, useMemo, useRef, 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;
  bootstrapError: string | null;
  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>;
  retryBootstrap: () => Promise<void>;
  resetLocalSession: () => Promise<void>;
};

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

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;
}

function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
  return new Promise((resolve, reject) => {
    const timeout = setTimeout(() => reject(new Error(message)), timeoutMs);

    promise
      .then(resolve)
      .catch(reject)
      .finally(() => clearTimeout(timeout));
  });
}

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [isBootstrapping, setIsBootstrapping] = useState(true);
  const [bootstrapError, setBootstrapError] = useState<string | null>(null);
  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 bootstrapRunRef = useRef(0);

  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);
    setBootstrapError(null);
    resetSession();
  }, [resetSession]);

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

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

  const resetLocalSession = useCallback(async () => {
    setToken(null);
    setBootstrapError(null);
    setIsBootstrapping(false);
    resetSession();
    void clearStoredToken().catch(() => undefined);
  }, [resetSession]);

  const bootstrap = useCallback(async () => {
    const runId = bootstrapRunRef.current + 1;
    bootstrapRunRef.current = runId;
    setIsBootstrapping(true);
    setBootstrapError(null);

    try {
      await withTimeout(
        (async () => {
          await hydrateApiBaseUrl();
          const storedToken = await getStoredToken();

          if (!storedToken) {
            if (bootstrapRunRef.current !== runId) return;
            setToken(null);
            resetSession();
            return;
          }

          await hydrateSession(storedToken);

          if (bootstrapRunRef.current !== runId) return;
          setToken(storedToken);
        })(),
        BOOTSTRAP_TIMEOUT_MS,
        'Die mobile Session konnte nicht rechtzeitig initialisiert werden.'
      );
    } catch (err) {
      if (bootstrapRunRef.current === runId) {
        setToken(null);
        resetSession();
        setBootstrapError(err instanceof Error ? err.message : 'Die mobile Session konnte nicht initialisiert werden.');
        void clearStoredToken().catch(() => undefined);
      }
    } finally {
      if (bootstrapRunRef.current === runId) {
        setIsBootstrapping(false);
      }
    }
  }, [hydrateSession, resetSession]);

  useEffect(() => {
    void bootstrap();
  }, [bootstrap]);

  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,
      bootstrapError,
      token,
      user,
      tenants,
      activeTenantId,
      activeTenant,
      profile,
      permissions,
      requiresTenantSelection,
      login,
      logout,
      refreshUser,
      switchTenant,
      retryBootstrap: bootstrap,
      resetLocalSession,
    }),
    [
      activeTenant,
      activeTenantId,
      bootstrap,
      bootstrapError,
      isBootstrapping,
      login,
      logout,
      permissions,
      profile,
      refreshUser,
      requiresTenantSelection,
      resetLocalSession,
      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;
}
