/// Service d'Authentification Keycloak via WebView
/// 
/// Implémentation professionnelle et sécurisée de l'authentification OAuth2/OIDC
/// avec Keycloak utilisant WebView pour contourner les limitations HTTPS de flutter_appauth.
/// 
/// Fonctionnalités :
/// - Flow OAuth2 Authorization Code avec PKCE
/// - Gestion sécurisée des tokens JWT
/// - Support HTTP/HTTPS
/// - Gestion complète des erreurs et timeouts
/// - Validation rigoureuse des paramètres
/// - Logging détaillé pour le debugging
library keycloak_webview_auth_service;

import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:crypto/crypto.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:http/http.dart' as http;
import 'package:jwt_decoder/jwt_decoder.dart';
import '../models/user.dart';
import '../models/user_role.dart';
import 'keycloak_role_mapper.dart';
import '../../../../core/config/environment.dart';

/// Configuration Keycloak pour l'authentification WebView
class KeycloakWebViewConfig {
  /// URL de base de l'instance Keycloak (depuis AppConfig)
  static String get baseUrl => AppConfig.keycloakBaseUrl;
  
  /// Realm UnionFlow
  static const String realm = 'unionflow';
  
  /// Client ID pour l'application mobile
  static const String clientId = 'unionflow-mobile';
  
  /// URL de redirection après authentification
  static const String redirectUrl = 'dev.lions.unionflow-mobile://auth/callback';
  
  /// Scopes OAuth2 demandés
  static const List<String> scopes = ['openid', 'profile', 'email', 'roles'];
  
  /// Timeout pour les requêtes HTTP (en secondes)
  static const int httpTimeoutSeconds = 30;
  
  /// Timeout pour l'authentification WebView (en secondes)
  static const int authTimeoutSeconds = 300; // 5 minutes
  
  /// Endpoints calculés
  static String get authorizationEndpoint => 
      '$baseUrl/realms/$realm/protocol/openid-connect/auth';
  
  static String get tokenEndpoint => 
      '$baseUrl/realms/$realm/protocol/openid-connect/token';
  
  static String get userInfoEndpoint => 
      '$baseUrl/realms/$realm/protocol/openid-connect/userinfo';
  
  static String get logoutEndpoint => 
      '$baseUrl/realms/$realm/protocol/openid-connect/logout';
  
  static String get jwksEndpoint => 
      '$baseUrl/realms/$realm/protocol/openid-connect/certs';
}

/// Résultat de l'authentification WebView
class WebViewAuthResult {
  final String accessToken;
  final String idToken;
  final String? refreshToken;
  final int expiresIn;
  final String tokenType;
  final List<String> scopes;
  
  const WebViewAuthResult({
    required this.accessToken,
    required this.idToken,
    this.refreshToken,
    required this.expiresIn,
    required this.tokenType,
    required this.scopes,
  });
  
  /// Création depuis la réponse token de Keycloak
  factory WebViewAuthResult.fromTokenResponse(Map<String, dynamic> response) {
    return WebViewAuthResult(
      accessToken: response['access_token'] ?? '',
      idToken: response['id_token'] ?? '',
      refreshToken: response['refresh_token'],
      expiresIn: response['expires_in'] ?? 3600,
      tokenType: response['token_type'] ?? 'Bearer',
      scopes: (response['scope'] as String?)?.split(' ') ?? [],
    );
  }
}

/// Exceptions spécifiques à l'authentification WebView
class KeycloakWebViewAuthException implements Exception {
  final String message;
  final String? code;
  final dynamic originalError;
  
  const KeycloakWebViewAuthException(
    this.message, {
    this.code,
    this.originalError,
  });
  
  @override
  String toString() => 'KeycloakWebViewAuthException: $message${code != null ? ' (Code: $code)' : ''}';
}

/// Service d'authentification Keycloak via WebView
/// 
/// Implémentation complète et sécurisée du flow OAuth2 Authorization Code avec PKCE
class KeycloakWebViewAuthService {
  // Stockage sécurisé des tokens
  static const FlutterSecureStorage _secureStorage = FlutterSecureStorage(
    aOptions: AndroidOptions(
      encryptedSharedPreferences: true,
    ),
    iOptions: IOSOptions(
      accessibility: KeychainAccessibility.first_unlock_this_device,
    ),
  );
  
  // Clés de stockage sécurisé — alignées avec KeycloakAuthService pour éviter IC-03
  // KeycloakAuthService lit 'kc_access' / 'kc_refresh' / 'kc_id' ; ApiClient aussi.
  static const String _accessTokenKey = 'kc_access';
  static const String _idTokenKey = 'kc_id';
  static const String _refreshTokenKey = 'kc_refresh';
  static const String _userInfoKey = 'keycloak_webview_user_info';
  static const String _authStateKey = 'keycloak_webview_auth_state';
  
  // Client HTTP avec timeout configuré
  static final http.Client _httpClient = http.Client();
  
  /// Génère un code verifier PKCE sécurisé
  static String _generateCodeVerifier() {
    const String charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
    final Random random = Random.secure();
    return List.generate(128, (i) => charset[random.nextInt(charset.length)]).join();
  }
  
  /// Génère le code challenge PKCE à partir du verifier
  static String _generateCodeChallenge(String verifier) {
    final List<int> bytes = utf8.encode(verifier);
    final Digest digest = sha256.convert(bytes);
    return base64Url.encode(digest.bytes).replaceAll('=', '');
  }
  
  /// Génère un state sécurisé pour la protection CSRF
  static String _generateState() {
    final Random random = Random.secure();
    final List<int> bytes = List.generate(32, (i) => random.nextInt(256));
    return base64Url.encode(bytes).replaceAll('=', '');
  }
  
  /// Construit l'URL d'autorisation Keycloak avec tous les paramètres
  static Future<Map<String, String>> _buildAuthorizationUrl() async {
    final String codeVerifier = _generateCodeVerifier();
    final String codeChallenge = _generateCodeChallenge(codeVerifier);
    final String state = _generateState();
    
    // Stocker les paramètres pour la validation ultérieure
    await _secureStorage.write(
      key: _authStateKey,
      value: jsonEncode({
        'code_verifier': codeVerifier,
        'state': state,
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      }),
    );
    
    final Map<String, String> params = {
      'response_type': 'code',
      'client_id': KeycloakWebViewConfig.clientId,
      'redirect_uri': KeycloakWebViewConfig.redirectUrl,
      'scope': KeycloakWebViewConfig.scopes.join(' '),
      'state': state,
      'code_challenge': codeChallenge,
      'code_challenge_method': 'S256',
      'kc_locale': 'fr',
      'prompt': 'login',
    };
    
    final String queryString = params.entries
        .map((e) => '${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}')
        .join('&');
    
    final String authUrl = '${KeycloakWebViewConfig.authorizationEndpoint}?$queryString';
    
    debugPrint('🔐 URL d\'autorisation générée: $authUrl');
    
    return {
      'url': authUrl,
      'state': state,
      'code_verifier': codeVerifier,
    };
  }
  
  /// Valide la réponse de redirection et extrait le code d'autorisation
  static Future<String> _validateCallbackAndExtractCode(
    String callbackUrl,
    String expectedState,
  ) async {
    debugPrint('🔍 Validation du callback: $callbackUrl');
    
    final Uri uri = Uri.parse(callbackUrl);
    
    // Vérifier que c'est bien notre URL de redirection
    if (!callbackUrl.startsWith(KeycloakWebViewConfig.redirectUrl)) {
      throw const KeycloakWebViewAuthException(
        'URL de callback invalide',
        code: 'INVALID_CALLBACK_URL',
      );
    }
    
    // Vérifier la présence d'erreurs
    final String? error = uri.queryParameters['error'];
    if (error != null) {
      final String? errorDescription = uri.queryParameters['error_description'];
      throw KeycloakWebViewAuthException(
        'Erreur d\'authentification: ${errorDescription ?? error}',
        code: error,
      );
    }
    
    // Valider le state pour la protection CSRF
    final String? receivedState = uri.queryParameters['state'];
    if (receivedState != expectedState) {
      throw const KeycloakWebViewAuthException(
        'State invalide - possible attaque CSRF',
        code: 'INVALID_STATE',
      );
    }
    
    // Extraire le code d'autorisation
    final String? code = uri.queryParameters['code'];
    if (code == null || code.isEmpty) {
      throw const KeycloakWebViewAuthException(
        'Code d\'autorisation manquant',
        code: 'MISSING_AUTH_CODE',
      );
    }
    
    debugPrint('✅ Code d\'autorisation extrait avec succès');
    return code;
  }

  /// Échange le code d'autorisation contre des tokens
  static Future<WebViewAuthResult> _exchangeCodeForTokens(
    String authCode,
    String codeVerifier,
  ) async {
    debugPrint('🔄 Échange du code d\'autorisation contre les tokens...');

    try {
      final Map<String, String> body = {
        'grant_type': 'authorization_code',
        'client_id': KeycloakWebViewConfig.clientId,
        'code': authCode,
        'redirect_uri': KeycloakWebViewConfig.redirectUrl,
        'code_verifier': codeVerifier,
      };

      final http.Response response = await _httpClient
          .post(
            Uri.parse(KeycloakWebViewConfig.tokenEndpoint),
            headers: {
              'Content-Type': 'application/x-www-form-urlencoded',
              'Accept': 'application/json',
            },
            body: body,
          )
          .timeout(const Duration(seconds: KeycloakWebViewConfig.httpTimeoutSeconds));

      debugPrint('📡 Réponse token endpoint: ${response.statusCode}');

      if (response.statusCode != 200) {
        final String errorBody = response.body;
        debugPrint('❌ Erreur échange tokens: $errorBody');

        Map<String, dynamic>? errorJson;
        try {
          errorJson = jsonDecode(errorBody);
        } catch (e) {
          // Ignore JSON parsing errors
        }

        final String errorMessage = errorJson?['error_description'] ??
                                   errorJson?['error'] ??
                                   'Erreur HTTP ${response.statusCode}';

        throw KeycloakWebViewAuthException(
          'Échec de l\'échange de tokens: $errorMessage',
          code: errorJson?['error'],
        );
      }

      final Map<String, dynamic> tokenResponse = jsonDecode(response.body);

      // Valider la présence des tokens requis
      if (!tokenResponse.containsKey('access_token') ||
          !tokenResponse.containsKey('id_token')) {
        throw const KeycloakWebViewAuthException(
          'Tokens manquants dans la réponse',
          code: 'MISSING_TOKENS',
        );
      }

      debugPrint('✅ Tokens reçus avec succès');
      return WebViewAuthResult.fromTokenResponse(tokenResponse);

    } on TimeoutException {
      throw const KeycloakWebViewAuthException(
        'Timeout lors de l\'échange des tokens',
        code: 'TIMEOUT',
      );
    } catch (e) {
      if (e is KeycloakWebViewAuthException) rethrow;

      throw KeycloakWebViewAuthException(
        'Erreur lors de l\'échange des tokens: $e',
        originalError: e,
      );
    }
  }

  /// Stocke les tokens de manière sécurisée
  static Future<void> _storeTokens(WebViewAuthResult authResult) async {
    debugPrint('💾 Stockage sécurisé des tokens...');

    try {
      await Future.wait([
        _secureStorage.write(key: _accessTokenKey, value: authResult.accessToken),
        _secureStorage.write(key: _idTokenKey, value: authResult.idToken),
        if (authResult.refreshToken != null)
          _secureStorage.write(key: _refreshTokenKey, value: authResult.refreshToken!),
      ]);

      debugPrint('✅ Tokens stockés avec succès');
    } catch (e) {
      throw KeycloakWebViewAuthException(
        'Erreur lors du stockage des tokens: $e',
        originalError: e,
      );
    }
  }

  /// Valide et parse un token JWT
  ///
  /// Stratégie de validation JWT :
  /// - Côté mobile : vérification de l'expiration et de l'issuer uniquement.
  ///   La signature JWT n'est PAS vérifiée côté mobile car :
  ///   1. Le token est envoyé au backend dans chaque requête API (header Authorization)
  ///   2. Le backend Quarkus valide la signature via JWKS (quarkus-oidc)
  ///   3. Toutes les communications passent par HTTPS en production
  ///   4. Le token est stocké dans FlutterSecureStorage (EncryptedSharedPreferences / Keychain)
  /// - Si une validation de signature locale est requise, implémenter via le
  ///   endpoint JWKS : `KeycloakWebViewConfig.jwksEndpoint`
  static Map<String, dynamic> _parseAndValidateJWT(String token, String tokenType) {
    try {
      // Vérifier l'expiration
      if (JwtDecoder.isExpired(token)) {
        throw KeycloakWebViewAuthException(
          '$tokenType expiré',
          code: 'TOKEN_EXPIRED',
        );
      }

      // Parser le payload
      final Map<String, dynamic> payload = JwtDecoder.decode(token);

      // Validations de base
      if (payload['iss'] == null) {
        throw const KeycloakWebViewAuthException(
          'Token JWT invalide: issuer manquant',
          code: 'INVALID_JWT',
        );
      }

      // Vérifier l'issuer
      final String expectedIssuer = '${KeycloakWebViewConfig.baseUrl}/realms/${KeycloakWebViewConfig.realm}';
      if (payload['iss'] != expectedIssuer) {
        throw KeycloakWebViewAuthException(
          'Token JWT invalide: issuer incorrect (attendu: $expectedIssuer, reçu: ${payload['iss']})',
          code: 'INVALID_ISSUER',
        );
      }

      debugPrint('✅ $tokenType validé avec succès');
      return payload;

    } catch (e) {
      if (e is KeycloakWebViewAuthException) rethrow;

      throw KeycloakWebViewAuthException(
        'Erreur lors de la validation du $tokenType: $e',
        originalError: e,
      );
    }
  }

  /// Méthode principale d'authentification
  ///
  /// Retourne les paramètres nécessaires pour lancer la WebView d'authentification
  static Future<Map<String, String>> prepareAuthentication() async {
    debugPrint('🚀 Préparation de l\'authentification WebView...');

    try {
      // Nettoyer les données d'authentification précédentes
      await clearAuthData();

      // Générer l'URL d'autorisation avec PKCE
      final Map<String, String> authParams = await _buildAuthorizationUrl();

      debugPrint('✅ Authentification préparée avec succès');
      return authParams;

    } catch (e) {
      throw KeycloakWebViewAuthException(
        'Erreur lors de la préparation de l\'authentification: $e',
        originalError: e,
      );
    }
  }

  /// Traite le callback de redirection et finalise l'authentification
  static Future<User> handleAuthCallback(String callbackUrl) async {
    debugPrint('🔄 Traitement du callback d\'authentification...');
    debugPrint('📋 URL de callback: $callbackUrl');

    try {
      // Récupérer les paramètres d'authentification stockés
      debugPrint('🔍 Récupération de l\'état d\'authentification...');
      final String? authStateJson = await _secureStorage.read(key: _authStateKey);
      if (authStateJson == null) {
        debugPrint('❌ État d\'authentification manquant');
        throw const KeycloakWebViewAuthException(
          'État d\'authentification manquant',
          code: 'MISSING_AUTH_STATE',
        );
      }

      final Map<String, dynamic> authState = jsonDecode(authStateJson);
      final String expectedState = authState['state'];
      final String codeVerifier = authState['code_verifier'];
      debugPrint('✅ État d\'authentification récupéré');

      // Valider le callback et extraire le code
      debugPrint('🔍 Validation du callback...');
      final String authCode = await _validateCallbackAndExtractCode(
        callbackUrl,
        expectedState,
      );
      debugPrint('✅ Code d\'autorisation extrait: ${authCode.substring(0, 10)}...');

      // Échanger le code contre des tokens
      debugPrint('🔄 Échange du code contre les tokens...');
      final WebViewAuthResult authResult = await _exchangeCodeForTokens(
        authCode,
        codeVerifier,
      );
      debugPrint('✅ Tokens reçus avec succès');

      // Stocker les tokens
      debugPrint('💾 Stockage des tokens...');
      await _storeTokens(authResult);
      debugPrint('✅ Tokens stockés');

      // Créer l'utilisateur depuis les tokens
      debugPrint('👤 Création de l\'utilisateur...');
      final User user = await _createUserFromTokens(authResult);
      debugPrint('✅ Utilisateur créé: ${user.fullName}');

      // Nettoyer l'état d'authentification temporaire
      await _secureStorage.delete(key: _authStateKey);

      debugPrint('🎉 Authentification WebView terminée avec succès');
      return user;

    } catch (e, stackTrace) {
      debugPrint('💥 Erreur lors du traitement du callback: $e');
      debugPrint('📋 Stack trace: $stackTrace');

      // Nettoyer en cas d'erreur
      await _secureStorage.delete(key: _authStateKey);

      if (e is KeycloakWebViewAuthException) rethrow;

      throw KeycloakWebViewAuthException(
        'Erreur lors du traitement du callback: $e',
        originalError: e,
      );
    }
  }

  /// Crée un utilisateur depuis les tokens JWT
  static Future<User> _createUserFromTokens(WebViewAuthResult authResult) async {
    debugPrint('👤 Création de l\'utilisateur depuis les tokens...');

    try {
      // Parser et valider les tokens
      final Map<String, dynamic> accessTokenPayload = _parseAndValidateJWT(
        authResult.accessToken,
        'Access Token',
      );
      final Map<String, dynamic> idTokenPayload = _parseAndValidateJWT(
        authResult.idToken,
        'ID Token',
      );

      // Extraire les informations utilisateur
      final String userId = idTokenPayload['sub'] ?? '';
      final String email = idTokenPayload['email'] ?? '';
      final String firstName = idTokenPayload['given_name'] ?? '';
      final String lastName = idTokenPayload['family_name'] ?? '';

      if (userId.isEmpty || email.isEmpty) {
        throw const KeycloakWebViewAuthException(
          'Informations utilisateur manquantes dans les tokens',
          code: 'MISSING_USER_INFO',
        );
      }

      // Extraire les rôles Keycloak
      final List<String> keycloakRoles = _extractKeycloakRoles(accessTokenPayload);

      // Mapper vers notre système de rôles
      final UserRole primaryRole = KeycloakRoleMapper.mapToUserRole(keycloakRoles);
      debugPrint('🔐 [AUTH WebView] Rôles: $keycloakRoles → UserRole: ${primaryRole.name} (${primaryRole.displayName})');
      final List<String> permissions = KeycloakRoleMapper.mapToPermissions(keycloakRoles);

      // Créer l'utilisateur
      final User user = User(
        id: userId,
        email: email,
        firstName: firstName,
        lastName: lastName,
        primaryRole: primaryRole,
        organizationContexts: const [],
        additionalPermissions: permissions,
        revokedPermissions: const [],
        preferences: const UserPreferences(
          language: 'fr',
          theme: 'system',
          notificationsEnabled: true,
          emailNotifications: true,
          pushNotifications: true,
          dashboardLayout: 'adaptive',
          timezone: 'Europe/Paris',
        ),
        lastLoginAt: DateTime.now(),
        createdAt: DateTime.now(),
        isActive: true,
      );

      // Stocker les informations utilisateur
      await _secureStorage.write(
        key: _userInfoKey,
        value: jsonEncode(user.toJson()),
      );

      debugPrint('✅ Utilisateur créé: ${user.fullName} (${user.primaryRole.displayName})');
      return user;

    } catch (e) {
      if (e is KeycloakWebViewAuthException) rethrow;

      throw KeycloakWebViewAuthException(
        'Erreur lors de la création de l\'utilisateur: $e',
        originalError: e,
      );
    }
  }

  /// Extrait les rôles Keycloak depuis le payload du token
  static List<String> _extractKeycloakRoles(Map<String, dynamic> tokenPayload) {
    try {
      final List<String> roles = <String>[];

      // Rôles realm
      final Map<String, dynamic>? realmAccess = tokenPayload['realm_access'];
      if (realmAccess != null && realmAccess['roles'] is List) {
        roles.addAll(List<String>.from(realmAccess['roles']));
      }

      // Rôles client
      final Map<String, dynamic>? resourceAccess = tokenPayload['resource_access'];
      if (resourceAccess != null) {
        final Map<String, dynamic>? clientAccess = resourceAccess[KeycloakWebViewConfig.clientId];
        if (clientAccess != null && clientAccess['roles'] is List) {
          roles.addAll(List<String>.from(clientAccess['roles']));
        }
      }

      // Filtrer les rôles système
      return roles.where((role) =>
        !role.startsWith('default-roles-') &&
        role != 'offline_access' &&
        role != 'uma_authorization'
      ).toList();

    } catch (e) {
      debugPrint('💥 Erreur extraction rôles: $e');
      return [];
    }
  }

  /// Nettoie toutes les données d'authentification
  static Future<void> clearAuthData() async {
    debugPrint('🧹 Nettoyage des données d\'authentification...');

    try {
      await Future.wait([
        _secureStorage.delete(key: _accessTokenKey),
        _secureStorage.delete(key: _idTokenKey),
        _secureStorage.delete(key: _refreshTokenKey),
        _secureStorage.delete(key: _userInfoKey),
        _secureStorage.delete(key: _authStateKey),
      ]);

      debugPrint('✅ Données d\'authentification nettoyées');
    } catch (e) {
      debugPrint('⚠️ Erreur lors du nettoyage: $e');
    }
  }

  /// Vérifie si l'utilisateur est authentifié
  static Future<bool> isAuthenticated() async {
    try {
      final String? accessToken = await _secureStorage.read(key: _accessTokenKey);

      if (accessToken == null) {
        return false;
      }

      // Vérifier si le token est expiré
      return !JwtDecoder.isExpired(accessToken);

    } catch (e) {
      debugPrint('💥 Erreur vérification authentification: $e');
      return false;
    }
  }

  /// Récupère l'utilisateur authentifié
  static Future<User?> getCurrentUser() async {
    try {
      final String? userInfoJson = await _secureStorage.read(key: _userInfoKey);

      if (userInfoJson == null) {
        return null;
      }

      final Map<String, dynamic> userJson = jsonDecode(userInfoJson);
      return User.fromJson(userJson);

    } catch (e) {
      debugPrint('💥 Erreur récupération utilisateur: $e');
      return null;
    }
  }

  /// Déconnecte l'utilisateur
  static Future<bool> logout() async {
    debugPrint('🚪 Déconnexion de l\'utilisateur...');

    try {
      // Nettoyer les données locales
      await clearAuthData();

      debugPrint('✅ Déconnexion réussie');
      return true;

    } catch (e) {
      debugPrint('💥 Erreur déconnexion: $e');
      return false;
    }
  }
}
