import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios';
import { toast } from 'react-toastify';
import useAuthStore from '../../store/useAuthStore';
import { logSecurityWarning, logDebug, logWarn } from '../utils/errorReporter';

const rawBase = import.meta.env.VITE_API_URL || 'http://localhost:8000';
const normalized = String(rawBase).replace(/\/$/, '');
const API_BASE_URL = /\/api(\/?$)/i.test(normalized)
  ? normalized
  : normalized + '/api';

// Validate HTTPS in production
if (import.meta.env.MODE === 'production' && !normalized.startsWith('https://')) {
  logSecurityWarning(
    'API URL is not using HTTPS in production! This exposes sensitive data to interception. Please configure VITE_API_URL to use HTTPS.',
    { apiUrl: normalized }
  );
}

const MAX_RETRIES = 3;
const RETRY_DELAY = 1000; 
// Note: 503 is excluded because it's used for "service unavailable" (like disabled features)
// and should not be retried - it's an intentional state, not a transient error
const RETRYABLE_STATUS_CODES = [408, 429, 500, 502, 504];

// Helper function to get CSRF token from cookies
const getCsrfToken = (): string | null => {
  if (typeof document === 'undefined') {
    return null;
  }
  
  // CSRF token cookie name (matches backend XSRF-TOKEN)
  const cookies = document.cookie.split(';');
  for (const cookie of cookies) {
    const [name, value] = cookie.trim().split('=');
    if (name === 'XSRF-TOKEN') {
      return decodeURIComponent(value);
    }
  }
  return null;
};

const apiClient = axios.create({
  baseURL: API_BASE_URL,
  headers: {
    'Content-Type': 'application/json',
  },
  timeout: 30000, 
  withCredentials: true, 
});

// Map to store AbortControllers for cancelable requests
const cancelTokenMap = new Map<string, AbortController>();

type RequestMetadata = {
  startTime: Date;
  requestId: string;
};

type ExtendedConfig = InternalAxiosRequestConfig & {
  _retry?: boolean;
  metadata?: RequestMetadata;
};

const retryRequest = async (
  error: AxiosError,
  retryCount: number = 0
): Promise<unknown> => {
  const config = error.config as ExtendedConfig;
  
  
  if (
    config._retry ||
    retryCount >= MAX_RETRIES ||
    !error.config ||
    !error.response ||
    !RETRYABLE_STATUS_CODES.includes(error.response.status)
  ) {
    return Promise.reject(error);
  }

  config._retry = true;
  
  
  const delay = RETRY_DELAY * Math.pow(2, retryCount);
  await new Promise(resolve => setTimeout(resolve, delay));

  logDebug(`Retrying request (${retryCount + 1}/${MAX_RETRIES})`, { url: config.url });
  
  return apiClient.request(config);
};

apiClient.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    // Note: We no longer check for tokens in localStorage since tokens are now
    // stored in httpOnly cookies. The backend will handle authentication via cookies.
    // We still need to ensure withCredentials is set (already done in apiClient config).
    
    // Normalize URL after checking
    if (config.url && typeof config.url === 'string') {
      if (config.url.startsWith('/api/')) {
        config.url = config.url.replace(/^\/api/, '');
      }
      config.url = config.url.replace(/\/\/+/, '/');
    }

    
    if (config.data instanceof FormData) {
      delete config.headers['Content-Type'];
    }

    // No longer setting Authorization header - backend reads from httpOnly cookies
    // The withCredentials: true in apiClient config ensures cookies are sent

    // Add CSRF token for state-changing requests (POST, PUT, DELETE, PATCH)
    // Skip for GET, HEAD, OPTIONS (safe methods)
    const method = (config.method || 'GET').toUpperCase();
    if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(method)) {
      // Skip CSRF token for auth endpoints (they're exempt)
      const path = config.url || '';
      const isAuthEndpoint = path.startsWith('/auth/') || path.startsWith('/api/auth/');
      
      if (!isAuthEndpoint) {
        const csrfToken = getCsrfToken();
        if (csrfToken) {
          config.headers['X-XSRF-TOKEN'] = csrfToken;
        } else {
          // CSRF token not found - this will be handled in error interceptor
          // The backend will return 403 and we'll retry after fetching the token
          logDebug('CSRF token not found in cookies. Request may fail and retry after fetching token.');
        }
      }
    }
    
    const requestId = crypto.randomUUID ? crypto.randomUUID() : 
      `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
    config.headers['X-Request-ID'] = requestId;

    
    // Create AbortController for request cancellation
    // Skip if signal is already provided
    if (!config.signal) {
      const abortController = new AbortController();
      config.signal = abortController.signal;
      
      // Store controller for potential cancellation
      const cancelKey = `${config.method || 'GET'}_${config.url}_${requestId}`;
      cancelTokenMap.set(cancelKey, abortController);
      
      // Clean up after request completes (set timeout to avoid memory leaks)
      setTimeout(() => {
        cancelTokenMap.delete(cancelKey);
      }, 60000); // Clean up after 60 seconds
    }

    
    (config as ExtendedConfig).metadata = { startTime: new Date(), requestId };

    return config;
  },
  (error: AxiosError) => {
    return Promise.reject(error);
  }
);

apiClient.interceptors.response.use(
  (response) => {
    
    const config = response.config as ExtendedConfig;
    
    // Clean up abort controller on successful response
    if (config.metadata?.requestId && config.url) {
      const cancelKey = `${config.method || 'GET'}_${config.url}_${config.metadata.requestId}`;
      cancelTokenMap.delete(cancelKey);
    }
    
    if (config.metadata?.startTime) {
      const duration = new Date().getTime() - config.metadata.startTime.getTime();
      if (duration > 1000) { 
        logWarn(`Slow request detected`, { url: config.url, duration: `${duration}ms` });
      }
    }

    
    const requestId = response.headers['x-request-id'];
    if (requestId) {
      logDebug(`Request completed`, { url: config.url, requestId });
    }

    return response;
  },
  async (error: AxiosError) => {
    const originalRequest = error.config as ExtendedConfig;
    
    // Clean up abort controller on error
    if (originalRequest && originalRequest.metadata?.requestId && originalRequest.url) {
      const cancelKey = `${originalRequest.method || 'GET'}_${originalRequest.url}_${originalRequest.metadata.requestId}`;
      cancelTokenMap.delete(cancelKey);
    }
    
    // Don't process cancellation errors
    if (error.code === 'ERR_CANCELED' || error.message === 'canceled') {
      return Promise.reject({
        ...error,
        message: 'Request was cancelled',
        isCancelled: true,
      });
    }

    
    if (!error.response) {
      if (error.code === 'ECONNABORTED') {
        return Promise.reject({
          ...error,
          message: 'Request timeout. Please check your connection and try again.',
        });
      }
      
      
      if (originalRequest && !originalRequest._retry) {
        return retryRequest(error);
      }

      logWarn('Network error', { error: error.message });
      return Promise.reject({
        ...error,
        message: 'Network error. Please check your internet connection.',
      });
    }

    const status = error.response.status;
    const requestId = error.response.headers['x-request-id'];

    
    if (status === 401) {
      // Clear auth state (tokens are in httpOnly cookies, backend will delete them)
      // Update auth store state
      useAuthStore.getState().logout().catch(() => {
        // Ignore logout errors, just clear the state
        useAuthStore.setState({
          token: null,
          userInfo: null,
          isAuthenticated: false,
          isLoading: false,
        });
      });
      
      // Dispatch custom event to trigger login modal
      window.dispatchEvent(new CustomEvent('auth:logout', { 
        detail: { message: 'Session expired. Please login again.' } 
      }));
      
      // SECURITY: Sanitize error message to prevent information disclosure
      const errorMessage = 'Session expired. Please login again.';
      return Promise.reject({
        ...error,
        message: errorMessage,
      });
    }

    
    if (status === 403) {
      // SECURITY: Sanitize error message - don't expose internal details
      const errorDataRaw = error.response?.data as unknown;
      const errorData =
        typeof errorDataRaw === 'object' && errorDataRaw !== null
          ? (errorDataRaw as Record<string, unknown>)
          : {};
      const rawDetail = typeof errorData.detail === 'string' ? errorData.detail : undefined;
      const rawMessage = typeof errorData.message === 'string' ? errorData.message : rawDetail || '';
      
      // Determine error type for better UX
      let errorMessage = 'You do not have permission to access this resource.';
      let shouldRetry = false;
      
      // Check for step-up authentication requirement
      const isStepUpRequired = 
        errorData.error === 'step_up_required' ||
        (typeof errorData.detail === 'object' && errorData.detail !== null && 'error' in errorData.detail && (errorData.detail as { error?: unknown }).error === 'step_up_required') ||
        (typeof errorData.detail === 'string' && errorData.detail.includes('Step-up authentication required'));
      
      // Check for MFA requirement error
      const isMfaRequired = 
        errorData.error === 'mfa_required' || 
        errorData.requires_mfa_setup === true ||
        (typeof errorData.detail === 'string' && errorData.detail.includes('Multi-factor authentication is required')) ||
        (typeof errorData.detail === 'object' && errorData.detail !== null && 'error' in errorData.detail && (errorData.detail as { error?: unknown }).error === 'mfa_required');
      
      if (isStepUpRequired) {
        // Step-up authentication required - dispatch event for UI to handle
        const actionDescription: string =
          (typeof errorData.detail === 'object' && errorData.detail !== null && 'action' in errorData.detail
            ? String((errorData.detail as { action?: unknown }).action ?? 'this action')
            : typeof errorData.detail === 'string'
              ? errorData.detail
              : 'this action');
        
        const detailMessage =
          typeof errorData.detail === 'object' && errorData.detail !== null && 'message' in errorData.detail && typeof (errorData.detail as { message?: unknown }).message === 'string'
            ? (errorData.detail as { message?: string }).message
            : undefined;
        errorMessage = detailMessage ?? `Step-up authentication required for ${actionDescription}. Please verify your identity.`;
        
        // Create retry function for the original request
        const retryRequest = async () => {
          if (originalRequest && !originalRequest._retry) {
            // Mark as retry to prevent infinite loops
            originalRequest._retry = true;
            // Retry the original request
            return apiClient.request(originalRequest);
          }
        };
        
        // Dispatch custom event for step-up authentication
        window.dispatchEvent(new CustomEvent('auth:step-up-required', {
          detail: {
            action: actionDescription,
            message: errorMessage,
            originalRequest: retryRequest,
          }
        }));
        
        return Promise.reject({
          ...error,
          message: errorMessage,
          requiresStepUp: true,
          stepUpAction: actionDescription,
        });
      } else if (isMfaRequired) {
        // Get user info to determine redirect path
        try {
          const userInfoStr = localStorage.getItem('userInfo');
          if (userInfoStr) {
            const userInfo = JSON.parse(userInfoStr);
            const userRole = userInfo?.role;
            
            // Redirect to appropriate security page based on role
            if (userRole === 'accountant') {
              errorMessage = 'Multi-factor authentication is required for financial access. Redirecting to setup...';
              toast.error('Multi-factor authentication is required for financial access. Please enable MFA to continue.');
              
              // Use setTimeout to allow error to be logged before redirect
              setTimeout(() => {
                window.location.href = '/accountant/security?setup_mfa=true&redirect=' + encodeURIComponent(window.location.pathname);
              }, 1000);
              
              return Promise.reject({
                ...error,
                message: errorMessage,
                requiresMfaSetup: true,
                redirectPath: '/accountant/security?setup_mfa=true',
              });
            } else if (userRole === 'admin') {
              errorMessage = 'Multi-factor authentication is required for financial access. Redirecting to setup...';
              toast.error('Multi-factor authentication is required for financial access. Please enable MFA to continue.');
              
              setTimeout(() => {
                window.location.href = '/admin/security?setup_mfa=true&redirect=' + encodeURIComponent(window.location.pathname);
              }, 1000);
              
              return Promise.reject({
                ...error,
                message: errorMessage,
                requiresMfaSetup: true,
                redirectPath: '/admin/security?setup_mfa=true',
              });
            }
          }
        } catch (e) {
          logDebug('Error parsing userInfo for MFA redirect', { error: e instanceof Error ? e.message : String(e) });
        }
        
        // Fallback message if we can't determine role
        errorMessage = 'Multi-factor authentication is required. Please enable MFA in your security settings.';
      } else if (rawMessage.includes('CSRF token') || rawMessage.toLowerCase().includes('csrf')) {
        errorMessage = 'Security validation failed. Please refresh the page and try again.';
        shouldRetry = true;
      } else if (rawMessage.toLowerCase().includes('forbidden') || rawMessage.toLowerCase().includes('permission')) {
        // Keep generic message for permission errors
        errorMessage = 'You do not have permission to access this resource.';
      }
      
      // Log 403 errors in development for debugging (not in production to avoid information disclosure)
      if (import.meta.env.DEV) {
        logDebug('403 Forbidden error', { 
          url: originalRequest?.url, 
          method: originalRequest?.method,
          rawMessage: rawMessage.substring(0, 100), // Limit length
          isMfaRequired,
        });
      }
      
      // Handle CSRF token missing/invalid errors - retry after getting token from error response
      if (shouldRetry && originalRequest && !originalRequest._retry) {
        // The backend sets the CSRF cookie in the error response, so wait a moment for browser to process it
        await new Promise(resolve => setTimeout(resolve, 200));
        
        // Check if we now have the CSRF token
        const csrfToken = getCsrfToken();
        if (csrfToken && originalRequest.headers) {
          // Retry the original request with the CSRF token
          originalRequest._retry = true;
          originalRequest.headers['X-XSRF-TOKEN'] = csrfToken;
          return apiClient.request(originalRequest);
        } else {
          // If still no token, try fetching it via GET request
          try {
            await apiClient.get('/health', { 
              headers: { ...originalRequest.headers },
              timeout: 5000
            });
            await new Promise(resolve => setTimeout(resolve, 100));
            
            const newToken = getCsrfToken();
            if (newToken && originalRequest.headers) {
              originalRequest._retry = true;
              originalRequest.headers['X-XSRF-TOKEN'] = newToken;
              return apiClient.request(originalRequest);
            }
          } catch (e) {
            // Ignore errors from token fetch attempts
            logDebug('Failed to fetch CSRF token', { error: e instanceof Error ? e.message : String(e) });
          }
        }
      }
      
      return Promise.reject({
        ...error,
        message: errorMessage,
      });
    }

    
    if (status === 404) {
      const data = error.response?.data as unknown;
      const errorMessage =
        typeof data === 'object' && data !== null && 'message' in data && typeof (data as { message?: unknown }).message === 'string'
          ? (data as { message?: string }).message
          : 'Resource not found.';
      return Promise.reject({
        ...error,
        message: errorMessage,
      });
    }

    
    if (status === 429) {
      const retryAfter = error.response.headers['retry-after'];
      return Promise.reject({
        ...error,
        message: `Too many requests. ${retryAfter ? `Please try again after ${retryAfter} seconds.` : 'Please try again later.'}`,
        retryAfter: retryAfter ? parseInt(retryAfter) : undefined,
      });
    }

    
    // Handle 503 (Service Unavailable) separately - often used for disabled features
    // Don't retry these as they're intentional states, not transient errors
    if (status === 503) {
      const errorData = error.response.data as unknown;
      const errorMessage =
        typeof errorData === 'object' && errorData !== null && 'detail' in errorData && typeof (errorData as { detail?: unknown }).detail === 'string'
          ? (errorData as { detail?: string }).detail
          : typeof errorData === 'object' && errorData !== null && 'message' in errorData && typeof (errorData as { message?: unknown }).message === 'string'
            ? (errorData as { message?: string }).message
            : 'Service temporarily unavailable';
      return Promise.reject({
        ...error,
        message: errorMessage,
        requestId,
      });
    }

    
    if (status >= 500 && status < 600) {
      if (originalRequest && !originalRequest._retry) {
        return retryRequest(error);
      }
      
      // SECURITY: Log detailed error only in development
      logDebug(`Server error`, { requestId: requestId || 'unknown', error: error.message });
      // SECURITY: Don't expose internal error details to users
      const errorMessage = 'Server error. Please try again later.';
      return Promise.reject({
        ...error,
        message: errorMessage,
        requestId,
      });
    }

    
    if (status === 400) {
      const errorData = error.response.data as unknown;
      // SECURITY: Sanitize validation errors - only show safe messages
      const safeMessage =
        typeof errorData === 'object' && errorData !== null && 'message' in errorData && typeof (errorData as { message?: unknown }).message === 'string'
          ? (errorData as { message?: string }).message
          : typeof errorData === 'object' && errorData !== null && 'errors' in errorData && Array.isArray((errorData as { errors?: unknown }).errors)
            ? (() => {
                const first = (errorData as { errors: unknown[] }).errors[0];
                return typeof first === 'object' && first !== null && 'message' in first && typeof (first as { message?: unknown }).message === 'string'
                  ? (first as { message?: string }).message
                  : 'Invalid request. Please check your input.';
              })()
            : 'Invalid request. Please check your input.';
      // Filter out sensitive error details
      const safeErrors =
        typeof errorData === 'object' && errorData !== null && 'errors' in errorData && Array.isArray((errorData as { errors?: unknown }).errors)
          ? (errorData as { errors: unknown[] }).errors.map((err) => {
              if (typeof err === 'object' && err !== null) {
                const field = 'field' in err && typeof (err as { field?: unknown }).field === 'string'
                  ? (err as { field?: string }).field
                  : 'path' in err && typeof (err as { path?: unknown }).path === 'string'
                    ? (err as { path?: string }).path
                    : undefined;
                const message = 'message' in err && typeof (err as { message?: unknown }).message === 'string'
                  ? (err as { message?: string }).message
                  : 'Invalid value';
                return { field, message };
              }
              return { field: undefined, message: 'Invalid value' };
            })
          : [];
      return Promise.reject({
        ...error,
        message: safeMessage,
        errors: safeErrors,
      });
    }

    
    // SECURITY: Sanitize error message - don't expose internal details
    const errorMessage = 'An error occurred. Please try again.';
    return Promise.reject({
      ...error,
      message: errorMessage,
      requestId,
    });
  }
);

/**
 * Cancel all pending requests (useful for cleanup on unmount)
 */
export const cancelAllPendingRequests = (): void => {
  cancelTokenMap.forEach((controller) => {
    controller.abort();
  });
  cancelTokenMap.clear();
};

/**
 * Cancel requests matching a pattern (useful for canceling specific endpoint requests)
 */
export const cancelRequestsByPattern = (pattern: string | RegExp): void => {
  const regex = typeof pattern === 'string' ? new RegExp(pattern, 'i') : pattern;
  const keysToCancel: string[] = [];
  
  cancelTokenMap.forEach((controller, key) => {
    if (regex.test(key)) {
      controller.abort();
      keysToCancel.push(key);
    }
  });
  
  keysToCancel.forEach((key) => cancelTokenMap.delete(key));
};

export default apiClient;
