import { useCallback, useEffect, useRef } from 'react';
import type { MutableRefObject } from 'react';
import type { AxiosInstance, AxiosError, InternalAxiosRequestConfig } from 'axios';
import { AxiosHeaders } from 'axios';

type AppStatus = string;

type AppDispatch = (action: { type: string; [key: string]: unknown }) => void;

type NotifyApiError = (error: unknown, fallbackMessage: string, variant?: string) => void;

type SetStatusMessage = (message: string, variant?: string) => void;

type SetLoading = (state: boolean) => void;

interface RetryableAxiosRequestConfig extends InternalAxiosRequestConfig {
  _retry?: boolean;
}

interface UseAuthManagerArgs {
  apiClient: AxiosInstance;
  token?: string | null;
  appStatus: AppStatus;
  appDispatch: AppDispatch;
  notifyApiError: NotifyApiError;
  setStatusMessage: SetStatusMessage;
  setLoading: SetLoading;
}

interface UseAuthManagerResult {
  tokenRef: MutableRefObject<string | null | undefined>;
  refreshAccessToken: () => Promise<string>;
  handleLogout: () => Promise<void>;
}

const ensureAxiosHeaders = (
  headers?: InternalAxiosRequestConfig['headers'],
): AxiosHeaders => {
  if (headers instanceof AxiosHeaders) {
    return headers;
  }
  return AxiosHeaders.from(headers || {});
};

const setHeaderAuthorization = (config: InternalAxiosRequestConfig, token: string): void => {
  const headers = ensureAxiosHeaders(config.headers);
  headers.set('Authorization', `Bearer ${token}`);
  config.headers = headers;
};

const useAuthManager = ({
  apiClient,
  token,
  appStatus,
  appDispatch,
  notifyApiError,
  setStatusMessage,
  setLoading,
}: UseAuthManagerArgs): UseAuthManagerResult => {
  const tokenRef = useRef<string | null | undefined>(token);
  const refreshPromiseRef = useRef<Promise<string> | null>(null);
  const initialRefreshAttemptedRef = useRef(Boolean(token));

  const refreshAccessToken = useCallback(async (): Promise<string> => {
    console.log('[Auth] Attempting to refresh access token…');
    appDispatch({ type: 'TOKEN_REFRESH_START' });
    try {
      const { data } = await apiClient.post<{ access_token?: string; tenant?: unknown }>('/auth/refresh');
      if (data?.access_token) {
        appDispatch({
          type: 'TOKEN_REFRESH_SUCCESS',
          token: data.access_token,
          tenant: data.tenant || null,
        });
        console.log('[Auth] Access token refreshed at', new Date().toISOString());
        return data.access_token;
      }
      throw new Error('Missing access token in refresh response');
    } catch (error) {
      console.warn('[Auth] Failed to refresh access token', error);
      appDispatch({ type: 'TOKEN_REFRESH_FAILURE', error: (error as Error)?.message || null });
      throw error;
    }
  }, [apiClient, appDispatch]);

  useEffect(() => {
    tokenRef.current = token;
  }, [token]);

  useEffect(() => {
    if (!token && !initialRefreshAttemptedRef.current && appStatus === 'logged-out') {
      initialRefreshAttemptedRef.current = true;
      console.log('[Auth] Attempting refresh at startup');
      refreshAccessToken().catch(() => {});
    }
  }, [token, appStatus, refreshAccessToken]);

  useEffect(() => {
    const requestInterceptor = apiClient.interceptors.request.use((config) => {
      const currentToken = tokenRef.current;
      if (currentToken) {
        const headers = ensureAxiosHeaders(config.headers);
        if (!headers.has('Authorization')) {
          headers.set('Authorization', `Bearer ${currentToken}`);
        }
        config.headers = headers;
      }
      return config;
    });

    const responseInterceptor = apiClient.interceptors.response.use(
      (response) => response,
      async (error) => {
        const axiosError = error as AxiosError & { config?: RetryableAxiosRequestConfig };
        const { response, config } = axiosError;
        if (!response || !config) {
          return Promise.reject(error);
        }

        const status = response.status;
        const url = String(config?.url ?? '');
        const isAuthRoute = url.includes('/auth/login') || url.includes('/auth/refresh');

        if (status === 401 && !config._retry && !isAuthRoute) {
          console.warn('[Auth] 401 received for', url, '- attempting token refresh');

          if (!refreshPromiseRef.current) {
            refreshPromiseRef.current = (async () => {
              try {
                return await refreshAccessToken();
              } finally {
                refreshPromiseRef.current = null;
              }
            })();
          }

          try {
            const newToken = await refreshPromiseRef.current;
            if (!newToken) {
              throw new Error('No token returned from refresh');
            }
            config._retry = true;
            setHeaderAuthorization(config, newToken);
            console.log('[Auth] Retrying original request', url);
            try {
              return await apiClient(config);
            } catch (retryError) {
              if ((retryError as AxiosError)?.response?.status === 401) {
                notifyApiError(retryError, 'Session expired. Please log in again.');
              }
              throw retryError;
            }
          } catch (refreshError) {
            console.warn('[Auth] Refresh failed, clearing session');
            notifyApiError(refreshError, 'Session expired. Please log in again.');
            return Promise.reject(refreshError);
          }
        }

        return Promise.reject(error);
      },
    );

    return () => {
      apiClient.interceptors.request.eject(requestInterceptor);
      apiClient.interceptors.response.eject(responseInterceptor);
    };
  }, [apiClient, notifyApiError, refreshAccessToken]);

  const handleLogout = useCallback(async () => {
    try {
      setLoading(true);
      await apiClient.post('/auth/logout');
    } catch (error) {
      console.warn('[Auth] Failed to revoke refresh token during logout', error);
    } finally {
      setLoading(false);
      appDispatch({ type: 'LOGOUT' });
      setStatusMessage('Logged out.', 'info');
    }
  }, [apiClient, appDispatch, setLoading, setStatusMessage]);

  return { tokenRef, refreshAccessToken, handleLogout };
};

export default useAuthManager;
