import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'dart:async';

/// Exception personnalisée pour les erreurs API avec méthodes d'affichage intégrées
class ApiException implements Exception {
  final String message;
  final int? statusCode;
  final String? errorCode;
  final Map<String, dynamic>? details;
  final Object? originalError;

  const ApiException(
    this.message, {
    this.statusCode,
    this.errorCode,
    this.details,
    this.originalError,
  });

  /// Créer une ApiException depuis une DioException
  factory ApiException.fromDioException(DioException dioException) {
    final response = dioException.response;
    final statusCode = response?.statusCode;

    // Essayer d'extraire le message de la réponse API
    String message = 'Erreur de communication avec le serveur';
    String? errorCode;
    Map<String, dynamic>? details;

    if (response?.data != null) {
      try {
        final data = response!.data as Map<String, dynamic>;
        debugPrint('🔍 API Error Response: $data');

        // Message spécifique de l'API
        if (data.containsKey('message')) {
          message = data['message'] as String;
        }

        // Code d'erreur spécifique
        if (data.containsKey('error_code')) {
          errorCode = data['error_code'] as String;
        }

        // Détails supplémentaires - peut être une Map ou une List
        if (data.containsKey('errors')) {
          final errorsData = data['errors'];
          if (errorsData is Map<String, dynamic>) {
            // Format: {field: [errors]}
            details = errorsData;
          } else if (errorsData is List) {
            // Format: [error1, error2, ...]
            details = {'errors': errorsData};
          }
          debugPrint('🔍 Validation Errors: $details');
        }
      } catch (e) {
        // Si on ne peut pas parser la réponse, utiliser le message par défaut
        debugPrint('⚠️ Impossible de parser la réponse d\'erreur: $e');
      }
    }

    // Messages par défaut selon le code de statut
    if (response?.data == null || message == 'Erreur de communication avec le serveur') {
      switch (statusCode) {
        case 400:
          message = 'Données invalides';
          break;
        case 401:
          message = 'Non autorisé : veuillez vous reconnecter';
          break;
        case 403:
          message = 'Accès interdit';
          break;
        case 404:
          message = 'Ressource non trouvée';
          break;
        case 409:
          message = 'Conflit : données déjà existantes';
          break;
        case 422:
          message = 'Données de validation incorrectes';
          break;
        case 500:
          message = 'Erreur serveur interne';
          break;
        case 502:
        case 503:
        case 504:
          message = 'Service temporairement indisponible';
          break;
        default:
          switch (dioException.type) {
            case DioExceptionType.connectionTimeout:
            case DioExceptionType.sendTimeout:
            case DioExceptionType.receiveTimeout:
              message = 'Délai d\'attente dépassé';
              break;
            case DioExceptionType.connectionError:
              message = 'Problème de connexion réseau';
              break;
            case DioExceptionType.cancel:
              message = 'Requête annulée';
              break;
            default:
              message = 'Erreur de communication avec le serveur';
          }
      }
    }

    return ApiException(
      message,
      statusCode: statusCode,
      errorCode: errorCode,
      details: details,
      originalError: dioException,
    );
  }

  /// Créer une ApiException depuis n'importe quelle erreur
  factory ApiException.fromError(Object error) {
    if (error is ApiException) {
      return error;
    }

    final errorString = error.toString();

    if (errorString.contains('SocketException') || errorString.contains('NetworkException')) {
      return const ApiException('Problème de connexion réseau');
    }
    if (errorString.contains('TimeoutException')) {
      return const ApiException('Délai d\'attente dépassé');
    }

    return ApiException('Erreur inattendue', originalError: error);
  }

  @override
  String toString() => message;

  /// Obtenir un message d'erreur formaté pour l'affichage
  String get displayMessage {
    debugPrint('🔍 [displayMessage] statusCode: $statusCode');
    debugPrint('🔍 [displayMessage] isValidationError: $isValidationError');
    debugPrint('🔍 [displayMessage] details: $details');
    debugPrint('🔍 [displayMessage] details != null: ${details != null}');
    debugPrint('🔍 [displayMessage] details!.isNotEmpty: ${details != null ? details!.isNotEmpty : "null"}');

    // Si c'est une erreur de validation avec des détails, formater le message
    if (isValidationError && details != null && details!.isNotEmpty) {
      debugPrint('✅ [displayMessage] Formatage des erreurs de validation');
      final buffer = StringBuffer(message);
      buffer.write('\n');

      details!.forEach((field, errors) {
        if (errors is List) {
          // Si le champ est 'errors', c'est une liste simple d'erreurs
          if (field == 'errors') {
            for (final error in errors) {
              buffer.write('• $error\n');
            }
          } else {
            // Sinon c'est un champ avec une liste d'erreurs
            for (final error in errors) {
              buffer.write('• $field: $error\n');
            }
          }
        } else {
          buffer.write('• $field: $errors\n');
        }
      });

      return buffer.toString().trim();
    }

    debugPrint('⚠️ [displayMessage] Retour du message simple');
    return message;
  }

  /// Vérifier si c'est une erreur de validation
  bool get isValidationError => statusCode == 422 || statusCode == 400;

  /// Vérifier si c'est une erreur d'authentification
  bool get isAuthError => statusCode == 401 || statusCode == 403;

  /// Vérifier si c'est une erreur de conflit (données déjà existantes)
  bool get isConflictError => statusCode == 409;

  /// Vérifier si c'est une erreur réseau
  bool get isNetworkError =>
      statusCode == null ||
      statusCode == 502 ||
      statusCode == 503 ||
      statusCode == 504 ||
      originalError is DioException && (originalError as DioException).type == DioExceptionType.connectionError;

  // === MÉTHODES D'AFFICHAGE INTÉGRÉES ===

  /// Afficher cette erreur (méthode d'instance)
  void show(BuildContext context, {Duration? duration}) {
    if (context.mounted) {
      final isInDialog = _isInDialog(context);

      if (isInDialog) {
        _showOverlaySnackBar(context, displayMessage, Colors.red, duration);
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(displayMessage),
            backgroundColor: Colors.red,
            duration: duration ?? const Duration(seconds: 5),
            action: SnackBarAction(
              label: 'Fermer',
              textColor: Colors.white,
              onPressed: () => ScaffoldMessenger.of(context).hideCurrentSnackBar(),
            ),
          ),
        );
      }
    }
  }

  /// Méthode statique pour afficher une erreur depuis n'importe quel objet
  static void showError(BuildContext context, Object error, {Duration? duration}) {
    final apiException = ApiException.fromError(error);
    apiException.show(context, duration: duration);
  }

  /// Méthode statique pour afficher un message de succès (compatible avec les Dialogs)
  static void showSuccess(BuildContext context, String message, {Duration? duration}) {
    if (context.mounted) {
      final isInDialog = _isInDialog(context);

      if (isInDialog) {
        _showOverlaySnackBar(context, message, Colors.green, duration);
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(message),
            backgroundColor: Colors.green,
            duration: duration ?? const Duration(seconds: 3),
          ),
        );
      }
    }
  }

  /// Vérifier si le contexte est dans un Dialog
  static bool _isInDialog(BuildContext context) {
    return context.findAncestorWidgetOfExactType<Dialog>() != null;
  }

  /// Afficher un SnackBar en overlay au-dessus de tout
  static void _showOverlaySnackBar(BuildContext context, String message, Color backgroundColor, Duration? duration) {
    final overlay = Overlay.of(context);
    late OverlayEntry overlayEntry;

    overlayEntry = OverlayEntry(
      builder: (context) => Positioned(
        top: MediaQuery.of(context).padding.top + 20, // En haut de l'écran
        left: 16,
        right: 16,
        child: Material(
          color: Colors.transparent,
          child: Container(
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
            decoration: BoxDecoration(
              color: backgroundColor,
              borderRadius: BorderRadius.circular(8),
              boxShadow: [
                BoxShadow(
                  color: Colors.black.withOpacity(0.3),
                  blurRadius: 8,
                  offset: const Offset(0, 4),
                ),
              ],
            ),
            child: Row(
              children: [
                Icon(
                  backgroundColor == Colors.red ? Icons.error_outline : Icons.check_circle_outline,
                  color: Colors.white,
                  size: 20,
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: Text(
                    message,
                    style: const TextStyle(
                      color: Colors.white,
                      fontSize: 14,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                ),
                IconButton(
                  icon: const Icon(Icons.close, color: Colors.white, size: 18),
                  onPressed: () => overlayEntry.remove(),
                  padding: EdgeInsets.zero,
                  constraints: const BoxConstraints(minWidth: 30, minHeight: 30),
                ),
              ],
            ),
          ),
        ),
      ),
    );

    overlay.insert(overlayEntry);

    // Auto-suppression après la durée spécifiée
    Timer(duration ?? const Duration(seconds: 5), () {
      if (overlayEntry.mounted) {
        overlayEntry.remove();
      }
    });
  }
}
