/// API interceptors for request/response handling
///
/// Provides interceptors for:
/// - Authentication token injection
/// - Request/response logging
/// - Error transformation
/// - Token refresh handling
library;

import 'dart:developer' as developer;

import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:worker/core/constants/api_constants.dart';
import 'package:worker/core/errors/exceptions.dart';

part 'api_interceptor.g.dart';

// ============================================================================
// Storage Keys
// ============================================================================

/// Keys for storing auth tokens in SharedPreferences
/// @deprecated Use AuthLocalDataSource with Hive instead
class AuthStorageKeys {
  static const String accessToken = 'auth_access_token';
  static const String refreshToken = 'auth_refresh_token';
  static const String tokenExpiry = 'auth_token_expiry';
}

// ============================================================================
// Auth Interceptor
// ============================================================================

/// Interceptor for adding Frappe/ERPNext session tokens to requests
///
/// Adds Cookie (with SID) and X-Frappe-CSRF-Token from FlutterSecureStorage.
/// Uses the centralized FrappeAuthService for session management.
class AuthInterceptor extends Interceptor {
  AuthInterceptor(this._prefs, this._dio, this._secureStorage);

  final SharedPreferences _prefs;
  final Dio _dio;
  final FlutterSecureStorage _secureStorage;

  @override
  void onRequest(
    RequestOptions options,
    RequestInterceptorHandler handler,
  ) async {
    // Check if this endpoint requires authentication
    if (_requiresAuth(options.path)) {
      // Get session data from secure storage
      final sid = await _secureStorage.read(key: 'frappe_sid');
      final csrfToken = await _secureStorage.read(key: 'frappe_csrf_token');

      if (sid != null && csrfToken != null) {
        // Only send sid in Cookie header - other fields are not needed
        options.headers['Cookie'] = 'sid=$sid';
        options.headers['X-Frappe-CSRF-Token'] = csrfToken;
      }

      // Legacy: Also check for access token (for backward compatibility)
      final token = await _getAccessToken();
      if (token != null) {
        options.headers['Authorization'] = 'Bearer $token';
      }
    }

    // Add language header
    options.headers['Accept-Language'] = ApiConstants.acceptLanguageVi;

    // Add content-type and accept headers if not already set
    options.headers['Content-Type'] ??= ApiConstants.contentTypeJson;
    options.headers['Accept'] ??= ApiConstants.acceptJson;

    handler.next(options);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) async {
    // Check if error is 401 Unauthorized
    if (err.response?.statusCode == 401) {
      // Try to refresh token
      final refreshed = await _refreshAccessToken();

      if (refreshed) {
        // Retry the original request with new token
        try {
          final response = await _retry(err.requestOptions);
          handler.resolve(response);
          return;
        } catch (e) {
          // If retry fails, continue with error
        }
      }
    }

    handler.next(err);
  }

  /// Check if endpoint requires authentication
  bool _requiresAuth(String path) {
    // Public endpoints that don't require auth
    final publicEndpoints = [
      ApiConstants.requestOtp,
      ApiConstants.verifyOtp,
      ApiConstants.register,
    ];

    return !publicEndpoints.any((endpoint) => path.contains(endpoint));
  }

  /// Get access token from storage
  Future<String?> _getAccessToken() async {
    return _prefs.getString(AuthStorageKeys.accessToken);
  }

  /// Get refresh token from storage
  Future<String?> _getRefreshToken() async {
    return _prefs.getString(AuthStorageKeys.refreshToken);
  }

  /// Check if token is expired
  // TODO: Use this method when implementing token refresh logic
  // Future<bool> _isTokenExpired() async {
  //   final expiryString = _prefs.getString(AuthStorageKeys.tokenExpiry);
  //   if (expiryString == null) return true;
  //
  //   final expiry = DateTime.tryParse(expiryString);
  //   if (expiry == null) return true;
  //
  //   return DateTime.now().isAfter(expiry);
  // }

  /// Refresh access token using refresh token
  Future<bool> _refreshAccessToken() async {
    try {
      final refreshToken = await _getRefreshToken();

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

      // Call refresh token endpoint
      final response = await _dio.post<Map<String, dynamic>>(
        '${ApiConstants.apiBaseUrl}${ApiConstants.refreshToken}',
        options: Options(headers: {'Authorization': 'Bearer $refreshToken'}),
      );

      if (response.statusCode == 200) {
        final data = response.data as Map<String, dynamic>;

        // Save new tokens
        await _prefs.setString(
          AuthStorageKeys.accessToken,
          data['accessToken'] as String,
        );

        if (data.containsKey('refreshToken')) {
          await _prefs.setString(
            AuthStorageKeys.refreshToken,
            data['refreshToken'] as String,
          );
        }

        if (data.containsKey('expiresAt')) {
          await _prefs.setString(
            AuthStorageKeys.tokenExpiry,
            data['expiresAt'] as String,
          );
        }

        return true;
      }

      return false;
    } catch (e) {
      developer.log(
        'Failed to refresh token',
        name: 'AuthInterceptor',
        error: e,
      );
      return false;
    }
  }

  /// Retry failed request with new token
  Future<Response<dynamic>> _retry(RequestOptions requestOptions) async {
    final token = await _getAccessToken();

    final options = Options(
      method: requestOptions.method,
      headers: {...requestOptions.headers, 'Authorization': 'Bearer $token'},
    );

    return _dio.request(
      requestOptions.path,
      data: requestOptions.data,
      queryParameters: requestOptions.queryParameters,
      options: options,
    );
  }
}

// ============================================================================
// Logging Interceptor
// ============================================================================

/// Interceptor for logging requests and responses in debug mode
class LoggingInterceptor extends Interceptor {
  LoggingInterceptor({
    this.enableRequestLogging = true,
    this.enableResponseLogging = true,
    this.enableErrorLogging = true,
  });

  final bool enableRequestLogging;
  final bool enableResponseLogging;
  final bool enableErrorLogging;

  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    if (enableRequestLogging) {
      developer.log(
        '╔══════════════════════════════════════════════════════════════',
        name: 'HTTP Request',
      );
      developer.log('║ ${options.method} ${options.uri}', name: 'HTTP Request');
      developer.log(
        '║ Headers: ${_sanitizeHeaders(options.headers)}',
        name: 'HTTP Request',
      );

      if (options.data != null) {
        developer.log(
          '║ Body: ${_sanitizeBody(options.data)}',
          name: 'HTTP Request',
        );
      }

      if (options.queryParameters.isNotEmpty) {
        developer.log(
          '║ Query Parameters: ${options.queryParameters}',
          name: 'HTTP Request',
        );
      }

      developer.log(
        '╚══════════════════════════════════════════════════════════════',
        name: 'HTTP Request',
      );
    }

    handler.next(options);
  }

  @override
  void onResponse(
    Response<dynamic> response,
    ResponseInterceptorHandler handler,
  ) {
    if (enableResponseLogging) {
      developer.log(
        '╔══════════════════════════════════════════════════════════════',
        name: 'HTTP Response',
      );
      developer.log(
        '║ ${response.requestOptions.method} ${response.requestOptions.uri}',
        name: 'HTTP Response',
      );
      developer.log(
        '║ Status Code: ${response.statusCode}',
        name: 'HTTP Response',
      );
      developer.log(
        '║ Headers: ${response.headers.map}',
        name: 'HTTP Response',
      );

      // Log full response data (not truncated for debugging)
      final responseData = response.data;
      if (responseData != null) {
        if (responseData is String) {
          developer.log('║ Response: $responseData', name: 'HTTP Response');
        } else {
          developer.log('║ Response: ${_truncateData(responseData, 2000)}', name: 'HTTP Response');
        }
      }

      developer.log(
        '╚══════════════════════════════════════════════════════════════',
        name: 'HTTP Response',
      );
    }

    handler.next(response);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    if (enableErrorLogging) {
      developer.log(
        '╔══════════════════════════════════════════════════════════════',
        name: 'HTTP Error',
      );
      developer.log(
        '║ ${err.requestOptions.method} ${err.requestOptions.uri}',
        name: 'HTTP Error',
      );
      developer.log('║ Error Type: ${err.type}', name: 'HTTP Error');
      developer.log(
        '║ Status Code: ${err.response?.statusCode}',
        name: 'HTTP Error',
      );
      developer.log('║ Message: ${err.message}', name: 'HTTP Error');

      if (err.response?.data != null) {
        developer.log(
          '║ Error Data: ${_truncateData(err.response?.data, 500)}',
          name: 'HTTP Error',
        );
      }

      developer.log(
        '╚══════════════════════════════════════════════════════════════',
        name: 'HTTP Error',
      );
    }

    handler.next(err);
  }

  /// Sanitize headers by hiding sensitive information
  Map<String, dynamic> _sanitizeHeaders(Map<String, dynamic> headers) {
    final sanitized = Map<String, dynamic>.from(headers);

    // Hide authorization token
    if (sanitized.containsKey('Authorization')) {
      sanitized['Authorization'] = '[HIDDEN]';
    }

    return sanitized;
  }

  /// Sanitize request body by hiding sensitive fields
  dynamic _sanitizeBody(dynamic body) {
    if (body is Map) {
      final sanitized = Map<dynamic, dynamic>.from(body);

      // List of sensitive field names
      final sensitiveFields = [
        'password',
        'otp',
        'token',
        'accessToken',
        'refreshToken',
        'secret',
        'apiKey',
      ];

      for (final field in sensitiveFields) {
        if (sanitized.containsKey(field)) {
          sanitized[field] = '[HIDDEN]';
        }
      }

      return sanitized;
    }

    return body;
  }

  /// Truncate data for logging to avoid huge logs
  String _truncateData(dynamic data, int maxLength) {
    final dataStr = data.toString();
    if (dataStr.length <= maxLength) {
      return dataStr;
    }
    return '${dataStr.substring(0, maxLength)}... [TRUNCATED]';
  }
}

// ============================================================================
// Error Transformer Interceptor
// ============================================================================

/// Interceptor for transforming Dio errors into custom exceptions
class ErrorTransformerInterceptor extends Interceptor {
  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    Exception exception;

    switch (err.type) {
      case DioExceptionType.connectionTimeout:
      case DioExceptionType.sendTimeout:
      case DioExceptionType.receiveTimeout:
        exception = const TimeoutException();
        break;

      case DioExceptionType.connectionError:
        exception = const NoInternetException();
        break;

      case DioExceptionType.badResponse:
        exception = _handleBadResponse(err.response);
        break;

      case DioExceptionType.cancel:
        exception = NetworkException('Yêu cầu đã bị hủy');
        break;

      case DioExceptionType.unknown:
        exception = NetworkException('Lỗi không xác định: ${err.message}');
        break;

      default:
        exception = NetworkException(err.message ?? 'Unknown error');
    }

    handler.reject(
      DioException(
        requestOptions: err.requestOptions,
        response: err.response,
        type: err.type,
        error: exception,
        message: exception.toString(),
      ),
    );
  }

  /// Handle bad response errors based on status code
  Exception _handleBadResponse(Response<dynamic>? response) {
    if (response == null) {
      return const ServerException();
    }

    final statusCode = response.statusCode ?? 0;
    final data = response.data;

    // Extract error message from response
    String? message;
    if (data is Map<String, dynamic>) {
      message =
          data['message'] as String? ??
          data['error'] as String? ??
          data['msg'] as String?;
    }

    switch (statusCode) {
      case 400:
        if (data is Map<String, dynamic> && data.containsKey('errors')) {
          final errors = data['errors'] as Map<String, dynamic>?;
          if (errors != null) {
            final validationErrors = errors.map(
              (key, value) => MapEntry(
                key,
                value is List ? value.cast<String>() : [value.toString()],
              ),
            );
            return ValidationException(
              message ?? 'Dữ liệu không hợp lệ',
              errors: validationErrors,
            );
          }
        }
        return BadRequestException(message ?? 'Yêu cầu không hợp lệ');

      case 401:
        if (message?.toLowerCase().contains('token') ?? false) {
          return const TokenExpiredException();
        }
        if (message?.toLowerCase().contains('otp') ?? false) {
          return const InvalidOTPException();
        }
        return UnauthorizedException(message ?? 'Phiên đăng nhập hết hạn');

      case 403:
        return const ForbiddenException();

      case 404:
        return NotFoundException(message ?? 'Không tìm thấy tài nguyên');

      case 409:
        return ConflictException(message ?? 'Tài nguyên đã tồn tại');

      case 422:
        if (data is Map<String, dynamic> && data.containsKey('errors')) {
          final errors = data['errors'] as Map<String, dynamic>?;
          if (errors != null) {
            final validationErrors = errors.map(
              (key, value) => MapEntry(
                key,
                value is List ? value.cast<String>() : [value.toString()],
              ),
            );
            return ValidationException(
              message ?? 'Dữ liệu không hợp lệ',
              errors: validationErrors,
            );
          }
        }
        return ValidationException(message ?? 'Dữ liệu không hợp lệ');

      case 429:
        final retryAfter = response.headers.value('retry-after');
        final retrySeconds = retryAfter != null
            ? int.tryParse(retryAfter)
            : null;
        return RateLimitException(message ?? 'Quá nhiều yêu cầu', retrySeconds);

      case 500:
      case 502:
      case 503:
      case 504:
        if (statusCode == 503) {
          return const ServiceUnavailableException();
        }
        return ServerException(message ?? 'Lỗi máy chủ', statusCode);

      default:
        return NetworkException(
          message ?? 'Lỗi mạng không xác định',
          statusCode: statusCode,
          data: data,
        );
    }
  }
}

// ============================================================================
// Riverpod Providers
// ============================================================================

/// Provider for SharedPreferences instance
@riverpod
Future<SharedPreferences> sharedPreferences(Ref ref) async {
  return await SharedPreferences.getInstance();
}

/// Provider for AuthInterceptor
@riverpod
Future<AuthInterceptor> authInterceptor(Ref ref, Dio dio) async {
  final prefs = await ref.watch(sharedPreferencesProvider.future);

  // Use FlutterSecureStorage for Frappe session
  const secureStorage = FlutterSecureStorage(
    aOptions: AndroidOptions(encryptedSharedPreferences: true),
    iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock),
  );

  return AuthInterceptor(prefs, dio, secureStorage);
}

/// Provider for LoggingInterceptor
@riverpod
LoggingInterceptor loggingInterceptor(Ref ref) {
  // Only enable logging in debug mode
  const bool isDebug = kDebugMode; // TODO: Replace with kDebugMode from Flutter

  return LoggingInterceptor(
    enableRequestLogging: true,
    enableResponseLogging: isDebug,
    enableErrorLogging: isDebug,
  );
}

/// Provider for ErrorTransformerInterceptor
@riverpod
ErrorTransformerInterceptor errorTransformerInterceptor(Ref ref) {
  return ErrorTransformerInterceptor();
}
