import 'dart:io';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../shared/design_system/tokens/app_colors.dart';
import 'package:injectable/injectable.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import '../../app/app.dart';
import '../config/environment.dart';
import '../security/spki_pinning_service.dart';
import '../di/injection.dart';
import '../error/error_handler.dart';
import '../utils/logger.dart';
import '../../features/authentication/presentation/bloc/auth_bloc.dart';
import '../../features/authentication/data/datasources/keycloak_auth_service.dart';
import 'org_context_service.dart';

/// Client réseau unifié basé sur Dio (Version DRY & Minimaliste).
@lazySingleton
class ApiClient {
  late final Dio _dio;

  static const FlutterSecureStorage _storage = FlutterSecureStorage(
    aOptions: AndroidOptions(encryptedSharedPreferences: true),
    iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock_this_device),
  );

  ApiClient(OrgContextService orgContextService) {
    _dio = Dio(
      BaseOptions(
        baseUrl: AppConfig.apiBaseUrl,
        connectTimeout: const Duration(seconds: 15),
        receiveTimeout: const Duration(seconds: 15),
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
        },
      ),
    );

    // Intercepteur de Token & Refresh automatique (doit être AVANT le logger)
    _dio.interceptors.add(
      InterceptorsWrapper(
        onRequest: (options, handler) async {
          // Utilise la clé 'kc_access' synchronisée avec KeycloakAuthService
          final token = await _storage.read(key: 'kc_access');
          if (token != null) {
            options.headers['Authorization'] = 'Bearer $token';
          }
          // Injecter l'organisation active si disponible
          if (orgContextService.hasContext) {
            options.headers[OrgContextService.headerName] =
                orgContextService.activeOrganisationId;
          }
          return handler.next(options);
        },
        onError: (DioException e, handler) async {
          // Évite une boucle infinie si le retry échoue aussi avec 401
          final isRetry = e.requestOptions.extra['custom_retry'] == true;
          
          if (e.response?.statusCode == 401 && !isRetry) {
            final responseBody = e.response?.data;
            debugPrint('🔑 [API] 401 Detected. Body: $responseBody. Attempting token refresh...');
            final refreshed = await _refreshToken();

            if (refreshed == true) {
              final token = await _storage.read(key: 'kc_access');
              if (token != null) {
                // Marque la requête comme étant un retry
                final options = e.requestOptions;
                options.extra['custom_retry'] = true;
                options.headers['Authorization'] = 'Bearer $token';

                try {
                  debugPrint('🔄 [API] Retrying request: ${options.path}');
                  final response = await _dio.fetch(options);
                  return handler.resolve(response);
                } on DioException catch (retryError) {
                  final retryBody = retryError.response?.data;
                  debugPrint('🚨 [API] Retry failed with status: ${retryError.response?.statusCode}. Body: $retryBody');
                  if (retryError.response?.statusCode == 401) {
                    debugPrint('🚪 [API] Persistent 401. Force Logout.');
                    _forceLogout();
                    return handler.reject(DioException(
                      requestOptions: retryError.requestOptions,
                      type: DioExceptionType.cancel,
                    ));
                  }
                  return handler.next(retryError);
                } catch (retryError) {
                  debugPrint('🚨 [API] Retry critical error: $retryError');
                  return handler.next(e);
                }
              }
            } else if (refreshed == false) {
              // Token définitivement invalide (400 Keycloak) → forcer déconnexion
              debugPrint('🚪 [API] Refresh failed (auth). Force Logout.');
              _forceLogout();
              return handler.reject(DioException(
                requestOptions: e.requestOptions,
                type: DioExceptionType.cancel,
              ));
            } else {
              // refreshed == null → erreur réseau transitoire, pas de logout
              debugPrint('⚠️ [API] Refresh failed (network). Propagating 401 without logout.');
            }
          }
          return handler.next(e);
        },
      ),
    );

    // SSL pinning en prod (MASVS v2 NETWORK)
    if (AppConfig.isProd && !kIsWeb) {
      _configureSslPinning();
    }

    // Intercepteur de Log (après le token pour voir le Bearer dans les logs)
    if (AppConfig.enableLogging) {
      _dio.interceptors.add(LogInterceptor(
        requestHeader: true,
        requestBody: true,
        responseBody: true,
        logPrint: (obj) => print('🌐 [API] $obj'),
      ));
    }
  }

  /// Configure SSL pinning pour les connexions prod (P0-NEW-21, 2026-04-25).
  ///
  /// Implémente un pinning par digest SHA-256 de SubjectPublicKeyInfo (SPKI), avec rotation
  /// dynamique via Firebase Remote Config. Conforme aux recommandations 2026 :
  ///   - Plus de pinning de cert statique (rotation cert prod = brick app garanti)
  ///   - SPKI digest survit aux rotations tant que la clé publique reste la même
  ///   - Multi-pin (leaf + backup + intermediate) pour transitions sans downtime
  ///
  /// Couplé à freeRASP + AppDeviceIntegrityService pour résister aux bypass Frida.
  void _configureSslPinning() {
    (_dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
      final client = HttpClient();
      client.badCertificateCallback = (cert, host, port) {
        return SpkiPinningService.instance.verifyCertificate(cert, host);
      };
      return client;
    };
  }

  void _forceLogout() {
    try {
      UnionFlowApp.scaffoldMessengerKey.currentState
        ?..clearSnackBars()
        ..showSnackBar(
          SnackBar(
            content: const Row(
              children: [
                Icon(Icons.lock_clock, color: Colors.white, size: 20),
                SizedBox(width: 10),
                Expanded(
                  child: Text(
                    'Session expirée. Vous avez été déconnecté automatiquement.',
                    style: TextStyle(color: Colors.white),
                  ),
                ),
              ],
            ),
            backgroundColor: AppColors.warning,
            duration: const Duration(seconds: 4),
            behavior: SnackBarBehavior.floating,
          ),
        );
      final authBloc = getIt<AuthBloc>();
      authBloc.add(const AuthLogoutRequested());
    } catch (e, st) {
      AppLogger.error(
        'ApiClient: force logout failed - ${ErrorHandler.getErrorMessage(e)}',
        error: e,
        stackTrace: st,
      );
    }
  }

  /// Retourne `true` si le token a été rafraîchi avec succès,
  /// `false` si le refresh token est invalide/expiré (logout nécessaire),
  /// `null` si erreur réseau transitoire (ne pas forcer le logout).
  Future<bool?> _refreshToken() async {
    try {
      final authService = getIt<KeycloakAuthService>();
      final newToken = await authService.refreshToken();
      if (newToken != null) return true;
      // refreshToken() retourne null après avoir appelé logout() (400 Keycloak)
      // OU après une erreur non-400 (réseau). Distinguer via le token en storage.
      final remaining = await _storage.read(key: 'kc_access');
      if (remaining == null) {
        // Tokens effacés → refresh a échoué pour cause d'authentification
        return false;
      }
      // Token toujours présent → erreur réseau transitoire
      return null;
    } catch (e, st) {
      AppLogger.error(
        'ApiClient: refresh token failed - ${ErrorHandler.getErrorMessage(e)}',
        error: e,
        stackTrace: st,
      );
      return null;
    }
  }

  Future<Response<T>> get<T>(String path, {Map<String, dynamic>? queryParameters, Options? options}) => _dio.get<T>(path, queryParameters: queryParameters, options: options);
  Future<Response<T>> post<T>(String path, {dynamic data, Map<String, dynamic>? queryParameters, Options? options}) => _dio.post<T>(path, data: data, queryParameters: queryParameters, options: options);
  Future<Response<T>> put<T>(String path, {dynamic data, Map<String, dynamic>? queryParameters, Options? options}) => _dio.put<T>(path, data: data, queryParameters: queryParameters, options: options);
  Future<Response<T>> delete<T>(String path, {dynamic data, Map<String, dynamic>? queryParameters, Options? options}) => _dio.delete<T>(path, data: data, queryParameters: queryParameters, options: options);
}
