import { AxiosError } from 'axios';
import { isTimeoutError, TIMEOUT_MESSAGES } from './timeoutHandler';
import { isOffline } from './offlineDetection';
import type { ApiError } from '@/schemas/apiSchemas';

/**
 * Helper de gestion d'erreurs API
 * Transforme les erreurs Axios brutes en objets ApiError standardisés
 * selon le format défini dans FRONTEND_INTEGRATION.md
 */

/**
 * Parse une erreur Axios en ApiError standardisé
 * @param error - Erreur Axios ou Error
 * @returns ApiError formaté selon le contrat backend
 */
// Structure interne de l'erreur backend
interface BackendErrorDetail {
  code?: number | string;
  message?: string;
  details?: unknown;
  retry_after?: number;
}

// Structure de réponse standardisée { success: false, error: ... }
interface StandardErrorResponse {
  success: boolean;
  error: BackendErrorDetail;
}

// Structure { error: ... } (Middleware différent)
interface NestedErrorResponse {
  error: BackendErrorDetail;
}

// Structure directe { code: ..., message: ... }
interface DirectErrorResponse {
  code: number | string;
  message: string;
  details?: unknown;
}

/**
 * Parse une erreur Axios en ApiError standardisé
 * @param error - Erreur Axios ou Error
 * @returns ApiError formaté selon le contrat backend
 */
export function parseApiError(error: unknown): ApiError {
  // Si c'est déjà une ApiError, la retourner telle quelle
  if (isApiError(error)) {
    return error;
  }

  // Si c'est une erreur Axios
  if (isAxiosError(error)) {
    const axiosError = error as AxiosError;
    const responseData = axiosError.response?.data;

    // Type guard helpers locaux
    const isStandardError = (data: unknown): data is StandardErrorResponse => {
      return (
        typeof data === 'object' &&
        data !== null &&
        'success' in data &&
        (data as any).success === false &&
        'error' in data
      );
    };

    const isNestedError = (data: unknown): data is NestedErrorResponse => {
      return (
        typeof data === 'object' &&
        data !== null &&
        'error' in data &&
        typeof (data as any).error === 'object'
      );
    };

    const isDirectError = (data: unknown): data is DirectErrorResponse => {
      return (
        typeof data === 'object' &&
        data !== null &&
        'code' in data &&
        'message' in data
      );
    };

    if (responseData) {
      // 1. Format standardisé { success: false, error: {...} }
      if (isStandardError(responseData)) {
        return normalizeApiError(responseData.error);
      }

      // 2. Format { error: {...} } sans success property
      if (isNestedError(responseData)) {
        const backendError = responseData.error;
        if (
          backendError &&
          ('code' in backendError || 'message' in backendError)
        ) {
          return normalizeApiError(backendError);
        }
      }

      // 3. Format direct { code: ..., message: ... }
      if (isDirectError(responseData)) {
        return normalizeApiError(responseData);
      }
    }

    // Erreur réseau (pas de réponse)
    // Action 3.5.1.1: Enhance network error detection - distinguish timeout vs connection refused vs offline
    if (axiosError.request && !axiosError.response) {
      // Check if it's a timeout error
      if (isTimeoutError(axiosError)) {
        return {
          code: 0,
          message: TIMEOUT_MESSAGES.timeout,
          timestamp: new Date().toISOString(),
        };
      }

      // Check for connection refused (server is down or unreachable)
      if (
        axiosError.code === 'ECONNREFUSED' ||
        axiosError.code === 'ERR_CONNECTION_REFUSED'
      ) {
        return {
          code: 0,
          message:
            'Connection refused: The server is not responding. Please try again later.',
          timestamp: new Date().toISOString(),
        };
      }

      // Action 3.5.1.3: Integrate offline detection with error handler
      // Check for network unreachable or no internet connection
      if (
        axiosError.code === 'ENETUNREACH' ||
        axiosError.code === 'ERR_NETWORK' ||
        axiosError.code === 'ERR_INTERNET_DISCONNECTED' ||
        isOffline()
      ) {
        return {
          code: 0,
          message:
            'No internet connection: Please check your network settings and try again.',
          timestamp: new Date().toISOString(),
        };
      }

      // Generic network error fallback
      return {
        code: 0,
        message:
          'Network error: Unable to connect to server. Please check your connection and try again.',
        timestamp: new Date().toISOString(),
      };
    }

    // Gestion spécifique des codes HTTP d'erreur
    const status = axiosError.response?.status;

    if (status === 429) {
      // Too Many Requests - Rate limiting
      const headers = axiosError.response?.headers || {};
      const data = responseData as {
        error?: { message?: string; retry_after?: number };
      } | null;

      const rateLimitLimit = headers['x-ratelimit-limit']
        ? parseInt(String(headers['x-ratelimit-limit']), 10)
        : undefined;
      const rateLimitRemaining = headers['x-ratelimit-remaining']
        ? parseInt(String(headers['x-ratelimit-remaining']), 10)
        : undefined;
      const rateLimitReset = headers['x-ratelimit-reset']
        ? parseInt(String(headers['x-ratelimit-reset']), 10)
        : undefined;
      const retryAfter = headers['retry-after']
        ? parseInt(String(headers['retry-after']), 10)
        : data?.error?.retry_after || 60;

      const resetTime = rateLimitReset
        ? new Date(rateLimitReset * 1000)
        : undefined;
      const secondsUntilReset = resetTime
        ? Math.max(0, Math.ceil((resetTime.getTime() - Date.now()) / 1000))
        : retryAfter;

      return {
        code: 429,
        message:
          data?.error?.message ||
          'Trop de requêtes. Veuillez patienter avant de réessayer.',
        timestamp: new Date().toISOString(),
        details: [
          {
            field: 'rate_limit',
            message: `Limite de ${rateLimitLimit || 'N/A'} requêtes atteinte. Réessayez dans ${secondsUntilReset} seconde${secondsUntilReset > 1 ? 's' : ''}.`,
          },
          ...(rateLimitRemaining !== undefined
            ? [
                {
                  field: 'remaining',
                  message: `${rateLimitRemaining} requête${rateLimitRemaining > 1 ? 's' : ''} restante${rateLimitRemaining > 1 ? 's' : ''}`,
                },
              ]
            : []),
        ],
        retry_after: secondsUntilReset,
      };
    }

    if (status === 503) {
      const data = responseData as {
        message?: string;
        details?: unknown;
      } | null;
      return {
        code: 503,
        message:
          data?.message ||
          'Service temporairement indisponible. Veuillez réessayer dans quelques instants.',
        timestamp: new Date().toISOString(),
        details: normalizeDetails(data?.details),
      };
    }

    if (status === 502) {
      const data = responseData as {
        message?: string;
        details?: unknown;
      } | null;
      return {
        code: 502,
        message:
          data?.message ||
          'Erreur de communication avec le serveur. Veuillez réessayer plus tard.',
        timestamp: new Date().toISOString(),
        details: normalizeDetails(data?.details),
      };
    }

    // Erreur HTTP sans format standardisé
    const data = responseData as { message?: string } | null;
    return {
      code: status || 0,
      message:
        data?.message || axiosError.message || 'An unexpected error occurred',
      timestamp: new Date().toISOString(),
    };
  }

  // Erreur JavaScript standard
  if (error instanceof Error) {
    return {
      code: 0,
      message: error.message || 'An unexpected error occurred',
      timestamp: new Date().toISOString(),
    };
  }

  // Erreur inconnue
  return {
    code: 0,
    message: 'An unexpected error occurred',
    timestamp: new Date().toISOString(),
  };
}

/**
 * Normalise un objet d'erreur backend en ApiError standardisé
 */
// Interface interne pour normalizeApiError dont on ne connait pas la structure exacte avant runtime
interface RawBackendError {
  code?: unknown;
  message?: unknown;
  details?: unknown;
  request_id?: unknown;
  timestamp?: unknown;
  context?: unknown;
}

interface ErrorDetail {
  field: string;
  message: string;
  value?: string;
}

/**
 * Normalise les détails d'une erreur
 */
function normalizeDetails(details: unknown): ErrorDetail[] | undefined {
  if (!Array.isArray(details)) {
    return undefined;
  }

  // Filtrer pour ne garder que les objets valides qui ressemblent à des ErrorDetail
  const validDetails = details.filter((item): item is ErrorDetail => {
    return (
      typeof item === 'object' &&
      item !== null &&
      'field' in item &&
      'message' in item &&
      typeof (item as any).field === 'string' &&
      typeof (item as any).message === 'string'
    );
  });

  return validDetails.length > 0 ? validDetails : undefined;
}

/**
 * Normalise le contexte d'une erreur
 */
function normalizeContext(context: unknown): Record<string, any> | undefined {
  if (
    typeof context === 'object' &&
    context !== null &&
    !Array.isArray(context)
  ) {
    return context as Record<string, any>;
  }
  return undefined;
}

/**
 * Normalise un objet d'erreur backend en ApiError standardisé
 */
function normalizeApiError(error: unknown): ApiError {
  const err = error as RawBackendError;
  return {
    code:
      typeof err.code === 'number'
        ? err.code
        : parseInt(String(err.code || 0), 10),
    message:
      typeof err.message === 'string' ? err.message : 'An error occurred',
    details: normalizeDetails(err.details),
    request_id: typeof err.request_id === 'string' ? err.request_id : undefined,
    timestamp:
      typeof err.timestamp === 'string'
        ? err.timestamp
        : new Date().toISOString(),
    context: normalizeContext(err.context),
  };
}

/**
 * Formate un message d'erreur pour l'affichage dans l'UI
 * @param error - ApiError
 * @param includeRequestId - Si true, inclut le request_id dans le message (pour debugging)
 * @returns Message formaté pour l'utilisateur
 */
export function formatErrorMessage(
  error: ApiError,
  includeRequestId: boolean = false,
): string {
  let message = error.message;

  // Si l'erreur a des détails de validation, les inclure
  if (
    error.details &&
    Array.isArray(error.details) &&
    error.details.length > 0
  ) {
    const detailsMessages = error.details
      .map((detail) => `${detail.field}: ${detail.message}`)
      .join(', ');
    message = `${error.message} (${detailsMessages})`;
  }

  // Action 5.3.1.1: Always include request_id when requested (not just in development)
  if (includeRequestId && error.request_id) {
    message = `${message} [Request ID: ${error.request_id}]`;
  }

  return message;
}

/**
 * Error category types for error handling strategy
 */
export type ErrorCategory =
  | 'network'
  | 'validation'
  | 'authentication'
  | 'authorization'
  | 'not_found'
  | 'rate_limit'
  | 'server_error'
  | 'timeout'
  | 'unknown';

/**
 * Categorizes an ApiError into a specific error category
 * Used for determining appropriate error handling strategy (display, retry, redirect, etc.)
 *
 * @param error - ApiError to categorize
 * @returns ErrorCategory indicating the type of error
 *
 * @example
 * ```tsx
 * const category = getErrorCategory(apiError);
 * if (category === 'network') {
 *   // Show offline indicator
 * } else if (category === 'authentication') {
 *   // Redirect to login
 * }
 * ```
 */
export function getErrorCategory(
  error: ApiError | Error | unknown,
): ErrorCategory {
  // Check for AxiosError patterns first (before ApiError)
  if (error && typeof error === 'object' && 'isAxiosError' in error) {
    const axiosError = error as AxiosError;
    // Network error (no response)
    if (!axiosError.response && axiosError.request) {
      return 'network';
    }
    // Timeout
    if (axiosError.code === 'ECONNABORTED' || axiosError.code === 'ETIMEDOUT') {
      return 'timeout';
    }
  }

  // Handle ApiError
  if (error && typeof error === 'object' && 'code' in error) {
    const apiError = error as ApiError;
    const code =
      typeof apiError.code === 'number'
        ? apiError.code
        : parseInt(String(apiError.code || 0), 10);

    // Network errors (code 0 typically indicates network issues)
    if (code === 0) {
      return 'network';
    }

    // HTTP status code based categorization
    if (code >= 400 && code < 500) {
      // Client errors
      if (code === 401) {
        return 'authentication';
      }
      if (code === 403) {
        return 'authorization';
      }
      if (code === 404) {
        return 'not_found';
      }
      if (code === 422) {
        // Validation errors typically have details array
        if (
          apiError.details &&
          Array.isArray(apiError.details) &&
          apiError.details.length > 0
        ) {
          return 'validation';
        }
        return 'validation';
      }
      if (code === 429) {
        return 'rate_limit';
      }
      // Other 4xx errors
      return 'validation';
    }

    if (code >= 500 && code < 600) {
      // Server errors
      if (code === 504 || code === 408) {
        return 'timeout';
      }
      return 'server_error';
    }
  }

  // Handle standard Error objects
  if (error instanceof Error) {
    const message = error.message.toLowerCase();
    const name = error.name.toLowerCase();

    // Network-related errors
    if (
      message.includes('network') ||
      message.includes('fetch') ||
      message.includes('connection') ||
      message.includes('offline') ||
      name === 'networkerror' ||
      name === 'typeerror'
    ) {
      return 'network';
    }

    // Timeout errors
    if (message.includes('timeout') || name === 'timeouterror') {
      return 'timeout';
    }

    // Abort errors
    if (message.includes('abort') || name === 'aborterror') {
      return 'network';
    }
  }

  return 'unknown';
}

/**
 * Extrait les erreurs de validation par champ
 * @param error - ApiError
 * @returns Record avec les erreurs par champ (field -> message)
 */
export function getValidationErrors(error: ApiError): Record<string, string> {
  if (!error.details || !Array.isArray(error.details)) {
    return {};
  }

  const errors: Record<string, string> = {};
  for (const detail of error.details) {
    if (detail.field && detail.message) {
      errors[detail.field] = detail.message;
    }
  }

  return errors;
}

/**
 * Vérifie si une erreur est une ApiError
 */
function isApiError(error: unknown): error is ApiError {
  return (
    typeof error === 'object' &&
    error !== null &&
    'code' in error &&
    'message' in error &&
    typeof (error as any).code === 'number' &&
    typeof (error as any).message === 'string'
  );
}

/**
 * Vérifie si une erreur est une AxiosError
 */
function isAxiosError(error: unknown): error is AxiosError {
  return (
    typeof error === 'object' &&
    error !== null &&
    'isAxiosError' in error &&
    (error as any).isAxiosError === true
  );
}
