/**
 * API Client Configuration with Interceptors
 *
 * This module configures the auto-generated API client with:
 * - Token refresh interceptor (prevents race conditions with singleton pattern)
 * - Request interceptor (adds Authorization header)
 * - Response interceptor (handles 401, 403, 429, 500 errors)
 *
 * IMPORTANT: Do NOT modify generated files. All customization happens here.
 *
 * @module lib/api/client
 */

import type { AxiosError, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
import { client } from './generated/client.gen';
import { refreshToken as refreshTokenFn } from './generated/sdk.gen';
import config from '@/config/app.config';

/**
 * Token refresh state management (singleton pattern)
 * Prevents race conditions when multiple requests fail with 401 simultaneously
 *
 * The refreshPromise acts as both the lock and the shared promise.
 * If it exists, a refresh is in progress - all concurrent requests wait for the same promise.
 */
let refreshPromise: Promise<string> | null = null;

/**
 * Auth store accessor
 * Dynamically imported to avoid circular dependencies
 * Checks for E2E test store injection before using production store
 *
 * Note: Tested via E2E tests when interceptors are invoked
 */
/* istanbul ignore next */
const getAuthStore = async () => {
  // Check for E2E test store injection (same pattern as AuthProvider)
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  if (typeof window !== 'undefined' && (window as any).__TEST_AUTH_STORE__) {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const testStore = (window as any).__TEST_AUTH_STORE__;
    // Test store must have getState() method for non-React contexts
    return testStore.getState();
  }

  // Production: use real Zustand store
  // Note: Dynamic import is acceptable here (non-React context, checks __TEST_AUTH_STORE__ first)
  const { useAuthStore } = await import('@/lib/stores/authStore');
  return useAuthStore.getState();
};

/**
 * Refresh access token using refresh token
 *
 * Note: Tested in E2E tests
 *
 * @returns Promise<string> New access token
 * @throws Error if refresh fails
 */
/* istanbul ignore next */
async function refreshAccessToken(): Promise<string> {
  // Singleton pattern: reuse in-flight refresh request
  // If a refresh is already in progress, return the existing promise
  if (refreshPromise) {
    return refreshPromise;
  }

  // Create and store the refresh promise immediately to prevent race conditions
  refreshPromise = (async () => {
    try {
      const authStore = await getAuthStore();
      const { refreshToken } = authStore;

      if (!refreshToken) {
        throw new Error('No refresh token available');
      }

      if (config.debug.api) {
        console.log('[API Client] Refreshing access token...');
      }

      // Use generated SDK function for refresh
      const response = await refreshTokenFn({
        body: { refresh_token: refreshToken },
        throwOnError: true,
      });

      const newAccessToken = response.data.access_token;
      const newRefreshToken = response.data.refresh_token || refreshToken;

      // Update tokens in store
      // Note: Token type from OpenAPI spec doesn't include expires_in,
      // but backend may return it. We handle both cases gracefully.
      await authStore.setTokens(
        newAccessToken,
        newRefreshToken,
        undefined // expires_in not in spec, will use default
      );

      if (config.debug.api) {
        console.log('[API Client] Token refreshed successfully');
      }

      return newAccessToken;
    } catch (error) {
      if (config.debug.api) {
        console.error('[API Client] Token refresh failed:', error);
      }

      // Clear auth state
      const authStore = await getAuthStore();
      await authStore.clearAuth();

      // Only redirect to login when not already on an auth route
      if (typeof window !== 'undefined') {
        const currentPath = window.location.pathname;
        const onAuthRoute =
          currentPath === '/login' ||
          currentPath === '/register' ||
          currentPath.startsWith('/password-reset');
        if (!onAuthRoute) {
          const returnUrl = currentPath ? `?returnUrl=${encodeURIComponent(currentPath)}` : '';
          window.location.href = `/login${returnUrl}`;
        }
      }

      throw error;
    } finally {
      // Clear the promise so future 401s will trigger a new refresh
      refreshPromise = null;
    }
  })();

  return refreshPromise;
}

/**
 * Request Interceptor
 * Adds Authorization header with access token to all requests
 *
 * Note: Interceptor behavior tested in E2E tests
 */
client.instance.interceptors.request.use(
  /* istanbul ignore next */ async (requestConfig: InternalAxiosRequestConfig) => {
    const authStore = await getAuthStore();
    const { accessToken } = authStore;

    // Do not attach Authorization header for auth endpoints
    const url = requestConfig.url || '';
    const isAuthEndpoint =
      url.includes('/auth/login') ||
      url.includes('/auth/register') ||
      url.includes('/auth/refresh') ||
      url.includes('/auth/password') ||
      url.includes('/password');

    // Add Authorization header if token exists and not hitting auth endpoints
    if (accessToken && requestConfig.headers && !isAuthEndpoint) {
      requestConfig.headers.Authorization = `Bearer ${accessToken}`;
    }

    if (config.debug.api) {
      console.log('[API Client] Request:', requestConfig.method?.toUpperCase(), requestConfig.url);
    }

    return requestConfig;
  },
  /* istanbul ignore next */ (error) => {
    if (config.debug.api) {
      console.error('[API Client] Request error:', error);
    }
    return Promise.reject(error);
  }
);

/**
 * Response Interceptor
 * Handles errors and token refresh
 *
 * Note: Interceptor behavior tested in E2E tests
 */
client.instance.interceptors.response.use(
  /* istanbul ignore next */ (response: AxiosResponse) => {
    if (config.debug.api) {
      console.log('[API Client] Response:', response.status, response.config.url);
    }
    return response;
  },
  /* istanbul ignore next */ async (error: AxiosError) => {
    const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean };

    if (config.debug.api) {
      console.error('[API Client] Response error:', error.response?.status, error.config?.url);
    }

    // Handle 401 Unauthorized - Token expired
    if (error.response?.status === 401 && originalRequest && !originalRequest._retry) {
      const url = originalRequest.url || '';
      const isAuthEndpoint =
        url.includes('/auth/login') ||
        url.includes('/auth/register') ||
        url.includes('/auth/password') ||
        url.includes('/password');

      // If the 401 is from auth endpoints, do not attempt refresh
      if (isAuthEndpoint) {
        return Promise.reject(error);
      }

      // If refresh endpoint itself fails with 401, clear auth and reject
      if (url.includes('/auth/refresh')) {
        const authStore = await getAuthStore();
        await authStore.clearAuth();
        return Promise.reject(error);
      }

      // Ensure we have a refresh token before attempting refresh
      const authStore = await getAuthStore();
      if (!authStore.refreshToken) {
        return Promise.reject(error);
      }

      originalRequest._retry = true;

      try {
        // Refresh token
        const newAccessToken = await refreshAccessToken();

        // Retry original request with new token
        if (originalRequest.headers) {
          originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
        }

        return client.instance(originalRequest);
      } catch (refreshError) {
        return Promise.reject(refreshError);
      }
    }

    // Handle 403 Forbidden
    if (error.response?.status === 403) {
      if (config.debug.api) {
        console.warn('[API Client] Access forbidden (403)');
      }
      // Let the component handle this (might be permission issue, not auth)
    }

    // Handle 429 Too Many Requests
    if (error.response?.status === 429) {
      if (config.debug.api) {
        console.warn('[API Client] Rate limit exceeded (429)');
      }
      // Add retry-after handling if needed in future
    }

    // Handle 500+ Server Errors
    if (error.response?.status && error.response.status >= 500) {
      if (config.debug.api) {
        console.error('[API Client] Server error:', error.response.status);
      }
      // Could add error tracking service integration here
    }

    return Promise.reject(error);
  }
);

/**
 * Configure the generated client with base settings
 */
client.setConfig({
  baseURL: config.api.url,
  timeout: config.api.timeout,
  headers: {
    'Content-Type': 'application/json',
  },
});

/**
 * Configured API client instance
 * Use this for all API calls
 */
export { client as apiClient };

/**
 * Re-export all SDK functions for convenience
 * These are already configured with interceptors
 */
export * from './generated/sdk.gen';

/**
 * Re-export types for convenience
 */
export type * from './generated/types.gen';
