/**
 * Refresh interceptor: on 401 errors, attempts a token refresh and retries the
 * original request. If the refresh also fails, clears tokens and re-throws.
 */

import type { AxiosInstance, AxiosError, InternalAxiosRequestConfig } from 'axios';
import { API_PATHS } from '@effigenix/config';
import type { TokenProvider } from '../token-provider.js';
import { RefreshTokenExpiredError } from '../errors.js';

/** Marker on request config to prevent infinite refresh loops */
interface RetryConfig extends InternalAxiosRequestConfig {
  _retried?: boolean;
}

interface LoginResponse {
  accessToken: string;
  refreshToken: string;
  expiresAt: string;
}

export function setupRefreshInterceptor(client: AxiosInstance, tokenProvider: TokenProvider): void {
  client.interceptors.response.use(
    undefined,
    async (error: unknown) => {
      // Only process AxiosErrors with a response
      const axiosError = error as AxiosError;
      if (!axiosError.isAxiosError) {
        return Promise.reject(error);
      }

      const config = axiosError.config as RetryConfig | undefined;

      // Only handle 401 errors that haven't been retried yet and have a config
      if (axiosError.response?.status !== 401 || config?._retried || !config) {
        return Promise.reject(error);
      }

      // Skip refresh for auth endpoints themselves (login/refresh)
      const url = config.url ?? '';
      if (url.includes(API_PATHS.auth.login) || url.includes(API_PATHS.auth.refresh)) {
        return Promise.reject(error);
      }

      config._retried = true;

      const refreshToken = await tokenProvider.getRefreshToken();
      if (!refreshToken) {
        await tokenProvider.clearTokens();
        return Promise.reject(new RefreshTokenExpiredError());
      }

      try {
        // Use the same client so MockAdapter (tests) and auth interceptor work,
        // but mark _retried on a separate config so we don't loop.
        const response = await client.post<LoginResponse>(API_PATHS.auth.refresh, { refreshToken });
        const { accessToken, refreshToken: newRefreshToken, expiresAt } = response.data;
        await tokenProvider.saveTokens(accessToken, newRefreshToken, expiresAt);

        // Retry the original request with the new access token
        config.headers.Authorization = `Bearer ${accessToken}`;
        return await client.request(config);
      } catch {
        await tokenProvider.clearTokens();
        return Promise.reject(new RefreshTokenExpiredError());
      }
    },
  );
}
