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

import {
  fetchMe,
  loginWithEmailPassword,
  logoutSession,
  MeResponse,
  isAuthenticationError,
  refreshAuthSession,
  switchTenantRequest,
  Tenant,
} from '@/src/lib/api';
import { hydrateApiBaseUrl } from '@/src/lib/server-config';
import {
  clearStoredToken,
  getStoredSession,
  setStoredSession,
  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;
const SESSION_REFRESH_INTERVAL_MS = 5 * 60 * 60 * 1000;

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 [refreshToken, setRefreshToken] = 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 () => {
    if (refreshToken) {
      void logoutSession(refreshToken).catch(() => undefined);
    }
    await clearStoredToken();
    setToken(null);
    setRefreshToken(null);
    setBootstrapError(null);
    resetSession();
  }, [refreshToken, 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);
    setRefreshToken(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 storedSession = await getStoredSession();

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

          let activeSession = storedSession;
          try {
            await hydrateSession(activeSession.token);
          } catch {
            activeSession = await refreshAuthSession(activeSession.refreshToken);
            await setStoredSession(activeSession);
            await hydrateSession(activeSession.token);
          }

          if (bootstrapRunRef.current !== runId) return;
          setToken(activeSession.token);
          setRefreshToken(activeSession.refreshToken);
        })(),
        BOOTSTRAP_TIMEOUT_MS,
        'Die mobile Session konnte nicht rechtzeitig initialisiert werden.'
      );
    } catch (err) {
      if (bootstrapRunRef.current === runId) {
        setToken(null);
        setRefreshToken(null);
        resetSession();
        setBootstrapError(err instanceof Error ? err.message : 'Die mobile Session konnte nicht initialisiert werden.');
        if (isAuthenticationError(err)) {
          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 session = await loginWithEmailPassword(email, password);
      await setStoredSession(session);
      await hydrateSession(session.token);
      setToken(session.token);
      setRefreshToken(session.refreshToken);
    },
    [hydrateSession]
  );

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

      const session = await switchTenantRequest(tenantId, token, refreshToken);
      await setStoredSession(session);
      await hydrateSession(session.token);
      setToken(session.token);
      setRefreshToken(session.refreshToken);
    },
    [token, refreshToken, hydrateSession]
  );

  useEffect(() => {
    if (!refreshToken) return;

    const interval = setInterval(() => {
      void (async () => {
        try {
          const session = await refreshAuthSession(refreshToken);
          await setStoredSession(session);
          setToken(session.token);
          setRefreshToken(session.refreshToken);
        } catch {
          // Bei einem temporären Netzfehler bleibt die gespeicherte Session erhalten.
        }
      })();
    }, SESSION_REFRESH_INTERVAL_MS);

    return () => clearInterval(interval);
  }, [refreshToken]);

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