import axios, { AxiosError, AxiosInstance, InternalAxiosRequestConfig, AxiosResponse } from 'axios';

// API Response types
export interface ApiResponse<T = unknown> {
  success: boolean;
  data: T;
  statusCode?: number;
  timestamp?: string;
}

export interface ApiErrorResponse {
  success: false;
  statusCode: number;
  timestamp: string;
  path: string;
  method: string;
  error: string;
  message: string | string[];
  errors?: Array<{ field: string; errors: string[] }>;
}

// Token refresh state
let isRefreshing = false;
let isLoggingOut = false;
let failedQueue: Array<{
  resolve: (value: AxiosResponse) => void;
  reject: (error: AxiosError) => void;
  config: InternalAxiosRequestConfig;
}> = [];

// Reset the logging-out state. Call this when starting a new login session
// to ensure the API interceptor is no longer blocking requests.
export const resetLogoutState = () => {
  isLoggingOut = false;
};

// Force logout: redirect to /logout page which handles everything
const forceLogout = () => {
  if (isLoggingOut) return;
  if (typeof window !== 'undefined') {
    // Don't redirect to /logout if we're already on it or on /login
    const path = window.location.pathname;
    if (path === '/logout' || path === '/login') return;
    isLoggingOut = true;
    localStorage.setItem('isLoggingOut', 'true');
    window.location.href = '/logout';
  }
};

const processQueue = (error: AxiosError | null, newToken?: string) => {
  failedQueue.forEach((prom) => {
    if (error) {
      prom.reject(error);
    } else {
      // Update the queued request with new token and retry
      if (newToken && prom.config.headers) {
        prom.config.headers.Authorization = `Bearer ${newToken}`;
      }
      // Resolve to trigger the .then() which will retry the request
      api(prom.config).then(prom.resolve).catch(prom.reject);
    }
  });
  failedQueue = [];
};

// Create axios instance
const api: AxiosInstance = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001/api/v1',
  timeout: 30000,
  headers: {
    'Content-Type': 'application/json',
  },
});

// Request interceptor
api.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    // Skip API calls during logout to prevent refresh loops
    // Check both module flag and localStorage flag (logout page sets localStorage directly)
    // But allow public endpoints (agent search, CMS, etc.) to work even during/after logout
    if (isLoggingOut || (typeof window !== 'undefined' && localStorage.getItem('isLoggingOut') === 'true')) {
      if (!isPublicEndpoint(config.url)) {
        return Promise.reject(new axios.Cancel('Logging out'));
      }
    }
    // Add auth token if available
    if (typeof window !== 'undefined') {
      const token = localStorage.getItem('accessToken');
      if (token && config.headers) {
        config.headers.Authorization = `Bearer ${token}`;
      }
    }
    return config;
  },
  (error: AxiosError) => {
    return Promise.reject(error);
  }
);

// Public API endpoints that don't require authentication (everything else requires auth)
const publicEndpoints = [
  '/auth/login',
  '/auth/register',
  '/auth/refresh',
  '/auth/forgot-password',
  '/auth/reset-password',
  '/auth/verify-email',
  '/auth/2fa/verify',
  '/auth/2fa/verify-backup',
  '/agent-types',
  '/agents',
  '/cms',
  '/contact',
  '/profile-fields/filterable',
  '/stripe/plans',
  '/upload/presigned-download-url',
];

// Check if URL is a public endpoint
const isPublicEndpoint = (url?: string): boolean => {
  if (!url) return false;
  return publicEndpoints.some(endpoint => url.includes(endpoint));
};

// Response interceptor with token refresh
api.interceptors.response.use(
  (response) => {
    return response;
  },
  async (error: AxiosError<ApiErrorResponse>) => {
    const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean };

    // Handle 401 Unauthorized
    if (error.response?.status === 401 && !originalRequest._retry) {
      // For public endpoints, just reject the error without redirecting to signout
      // The request will work without auth anyway
      if (isPublicEndpoint(originalRequest.url)) {
        // Remove the auth header and retry once
        if (originalRequest.headers) {
          delete originalRequest.headers.Authorization;
        }
        originalRequest._retry = true;
        return api(originalRequest);
      }

      // Don't try to refresh if this was the refresh token request itself
      if (originalRequest.url?.includes('/auth/refresh')) {
        forceLogout();
        return Promise.reject(error);
      }

      if (isRefreshing) {
        // If already refreshing, queue this request to be retried after refresh completes
        return new Promise<AxiosResponse>((resolve, reject) => {
          failedQueue.push({ resolve, reject, config: originalRequest });
        });
      }

      originalRequest._retry = true;
      isRefreshing = true;

      const refreshToken = typeof window !== 'undefined' ? localStorage.getItem('refreshToken') : null;

      if (!refreshToken) {
        // No refresh token available, force logout
        isRefreshing = false;
        processQueue(error);
        forceLogout();
        return Promise.reject(error);
      }

      try {
        // Try to refresh the token
        const response = await axios.post<ApiResponse<{ accessToken: string; refreshToken: string }>>(
          `${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001/api/v1'}/auth/refresh`,
          { refreshToken },
          { headers: { 'Content-Type': 'application/json' } }
        );

        const { accessToken: newAccessToken, refreshToken: newRefreshToken } = response.data.data;

        // Save new tokens
        if (typeof window !== 'undefined') {
          localStorage.setItem('accessToken', newAccessToken);
          localStorage.setItem('refreshToken', newRefreshToken);
        }

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

        // Process queued requests with new token
        processQueue(null, newAccessToken);
        isRefreshing = false;

        // Retry original request
        return api(originalRequest);
      } catch (refreshError) {
        // Refresh failed, force logout
        processQueue(refreshError as AxiosError);
        isRefreshing = false;
        forceLogout();
        return Promise.reject(refreshError);
      }
    }

    return Promise.reject(error);
  }
);

export default api;
