import 'dart:io';

import 'package:dio/dio.dart';
import 'package:image_picker/image_picker.dart';
import 'package:logger/logger.dart';
import 'package:sigap/src/cores/services/supabase_service.dart';
import 'package:sigap/src/features/auth/data/models/face_model.dart';
import 'package:sigap/src/utils/constants/api_urls.dart';
import 'package:sigap/src/utils/dio.client/dio_client.dart';
import 'package:sigap/src/utils/helpers/error_utils.dart';
import 'package:sigap/src/utils/validators/image_validator.dart';

/// Service class for interacting with Supabase Edge Functions for face recognition
class EdgeFunctionService {
  // Singleton instance
  static final EdgeFunctionService instance = EdgeFunctionService._();
  EdgeFunctionService._();

  // Logger
  final _logger = Logger();

  // Supabase client for Edge Function invocation
  final supabase = SupabaseService.instance.client;

  // Edge function names
  final String _detectFaceFunction = 'detect-face';
  final String _verifyFaceFunction = 'verify-face';

  // Edge function URLs
  static String get supabaseUrl => Endpoints.supabaseUrl;
  static String get supabaseKey => Endpoints.supabaseAnonKey;

  String get _detectFaceUrl => '$supabaseUrl/functions/v1/$_detectFaceFunction';
  String get _verifyFaceUrl => '$supabaseUrl/functions/v1/$_verifyFaceFunction';

  // Max retries
  final int _maxRetries = 0;

  /// Detects faces in an image using the Supabase Edge Function with retries
  Future<List<FaceModel>> detectFaces(XFile imageFile) async {
    int retries = 0;
    Exception? lastException;

    while (retries <= _maxRetries) {
      try {
        _logger.d('Calling edge function for face detection...');

        // Validate image first
        if (!ImageValidator.isValidImageFile(imageFile.path)) {
          throw EdgeFunctionException(
            code: 'invalid_image_format',
            message: 'Please upload a valid JPG or PNG image',
          );
        }

        // Create form data
        final formData = FormData();

        // Read file bytes
        final imageBytes = await File(imageFile.path).readAsBytes();

        // Add file with proper MIME type
        final mimeType = ImageValidator.getMimeType(imageFile.path);

        formData.files.add(
          MapEntry(
            'image',
            MultipartFile.fromBytes(
              imageBytes,
              filename: imageFile.name,
              contentType: mimeType,
            ),
          ),
        );

        // Set headers
        final options = Options(
          headers: {
            'Authorization': 'Bearer $supabaseKey',
            'Content-Type': 'multipart/form-data',
          },
        );

        // Send request
        final response = await DioClient().post(
          _detectFaceUrl,
          data: formData,
          options: options,
        );

        if (response.statusCode != 200) {
          throw EdgeFunctionException(
            code: 'http_error',
            message: 'HTTP ${response.statusCode}: ${response.data}',
          );
        }

        // Parse response
        final data = response.data;

        if (data['success'] != true) {
          throw EdgeFunctionException(
            code: 'api_error',
            message: data['error'] ?? 'Unknown API error',
          );
        }

        List<FaceModel> faces = [];
        final faceDetails = data['faceDetails'] as List;

        for (var i = 0; i < faceDetails.length; i++) {
          faces.add(FaceModel.fromEdgeFunction(imageFile, faceDetails[i]));
        }

        _logger.d('Successfully detected ${faces.length} faces');
        return faces;
      } on DioException catch (e) {
        _logger.e(
          'Dio error in face detection (attempt ${retries + 1}): ${e.message}',
        );

        String errorMessage = 'Network error occurred. Please try again later.';
        String errorCode = 'network_error';

        // Handle different Dio error types
        if (e.type == DioExceptionType.connectionTimeout ||
            e.type == DioExceptionType.sendTimeout ||
            e.type == DioExceptionType.receiveTimeout) {
          errorMessage =
              'Connection timed out. Please check your internet and try again.';
          errorCode = 'timeout';
        } else if (e.type == DioExceptionType.connectionError) {
          errorMessage =
              'No internet connection. Please check your connection and try again.';
          errorCode = 'connection_error';
        } else if (e.response != null) {
          // Server responded with an error status code
          final statusCode = e.response!.statusCode;
          final responseData = e.response!.data;

          if (statusCode == 500) {
            if (responseData is Map && responseData['error'] is String) {
              final serverError = responseData['error'];
              if (serverError.contains(
                'Missing required environment variables',
              )) {
                errorCode = 'server_config_error';
                errorMessage =
                    'The verification service is temporarily unavailable due to maintenance.';
              }
            } else {
              errorMessage = 'Server error occurred. Please try again later.';
            }
          }
        }

        lastException = EdgeFunctionException(
          code: errorCode,
          message: errorMessage,
          details: e.toString(),
        );

        retries++;
        if (retries <= _maxRetries) {
          await Future.delayed(Duration(seconds: retries * 2));
          _logger.d('Retrying face detection (attempt $retries)...');
        }
      } on EdgeFunctionException catch (e) {
        _logger.e(
          'Edge function error (attempt ${retries + 1}): ${e.toString()}',
        );
        lastException = e;
        retries++;

        if (retries <= _maxRetries) {
          await Future.delayed(Duration(seconds: retries * 2));
          _logger.d('Retrying face detection (attempt $retries)...');
        }
      } catch (e) {
        _logger.e(
          'Unexpected error in face detection (attempt ${retries + 1}): $e',
        );

        String userMessage =
            'Verification service error. Please try again later.';
        String errorCode = 'unknown_error';

        if (e is SocketException) {
          errorCode = 'network_error';
          userMessage =
              'Network connection issue. Please check your internet connection.';
        }

        lastException = EdgeFunctionException(
          code: errorCode,
          message: userMessage,
          details: e.toString(),
        );
        retries++;

        if (retries <= _maxRetries) {
          await Future.delayed(Duration(seconds: retries * 2));
          _logger.d('Retrying face detection (attempt $retries)...');
        }
      }
    }

    // If we get here, all retries failed
    _logger.e(
      'Face detection failed after $_maxRetries retries: $lastException',
    );
    throw lastException ??
        EdgeFunctionException(
          code: 'max_retries',
          message: 'Verification service unavailable. Please try again later.',
        );
  }

  /// Compares two face images and returns a comparison result with retries
  Future<FaceComparisonResult> compareFaces(
    XFile sourceImage,
    XFile targetImage, {
    double similarityThreshold = 70.0,
    FaceModel? sourceModel,
    FaceModel? targetModel,
  }) async {
    int retries = 0;
    Exception? lastException;

    while (retries <= _maxRetries) {
      try {
        _logger.d('Calling edge function for face comparison...');

        // Validate images first
        if (!ImageValidator.isValidImageFile(sourceImage.path)) {
          throw EdgeFunctionException(
            code: 'invalid_source_image',
            message: 'Source image must be a valid JPG or PNG file',
          );
        }

        if (!ImageValidator.isValidImageFile(targetImage.path)) {
          throw EdgeFunctionException(
            code: 'invalid_target_image',
            message: 'Selfie image must be a valid JPG or PNG file',
          );
        }

        // Create form data
        final formData = FormData();

        // Read file bytes
        final sourceBytes = await File(sourceImage.path).readAsBytes();
        final targetBytes = await File(targetImage.path).readAsBytes();

        // Get MIME types
        final sourceMimeType = ImageValidator.getMimeType(sourceImage.path);
        final targetMimeType = ImageValidator.getMimeType(targetImage.path);

        // Add files with proper MIME types
        formData.files.addAll([
          MapEntry(
            'idCard',
            MultipartFile.fromBytes(
              sourceBytes,
              filename: sourceImage.name,
              contentType: sourceMimeType,
            ),
          ),
          MapEntry(
            'selfie',
            MultipartFile.fromBytes(
              targetBytes,
              filename: targetImage.name,
              contentType: targetMimeType,
            ),
          ),
        ]);

        // Add similarity threshold
        formData.fields.add(
          MapEntry('similarity_threshold', similarityThreshold.toString()),
        );

        // Set headers
        final options = Options(
          headers: {
            'Authorization': 'Bearer $supabaseKey',
            'Content-Type': 'multipart/form-data',
          },
        );

        // Send request
        final response = await DioClient().post(
          _verifyFaceUrl,
          data: formData,
          options: options,
        );

        if (response.statusCode != 200) {
          throw EdgeFunctionException(
            code: 'http_error',
            message: 'HTTP ${response.statusCode}: ${response.data}',
          );
        }

        // Parse response
        final data = response.data;

        if (data['success'] != true) {
          throw EdgeFunctionException(
            code: 'api_error',
            message: data['error'] ?? 'Unknown API error',
          );
        }

        // Use existing models if provided, otherwise detect faces
        FaceModel sourceFace;
        FaceModel targetFace;

        if (sourceModel != null && targetModel != null) {
          // Use the provided models if they're valid
          sourceFace = sourceModel;
          targetFace = targetModel;
        } else {
          // For face comparison, we'll need to detect faces first to create our models
          List<FaceModel> sourceFaces = await detectFaces(sourceImage);
          List<FaceModel> targetFaces = await detectFaces(targetImage);

          if (sourceFaces.isEmpty || targetFaces.isEmpty) {
            return FaceComparisonResult.noMatch(
              sourceFaces.isEmpty ? FaceModel.empty() : sourceFaces.first,
              targetFaces.isEmpty ? FaceModel.empty() : targetFaces.first,
              message:
                  sourceFaces.isEmpty && targetFaces.isEmpty
                      ? 'No faces detected in either image'
                      : sourceFaces.isEmpty
                      ? 'No face detected in ID card image'
                      : 'No face detected in selfie image',
            );
          }

          sourceFace = sourceFaces.first;
          targetFace = targetFaces.first;
        }

        return FaceComparisonResult.fromEdgeFunction(
          sourceFace,
          targetFace,
          data,
        );
      } on DioException catch (e) {
        _logger.e(
          'Dio error in face comparison (attempt ${retries + 1}): ${e.message}',
        );

        String errorMessage = 'Network error occurred. Please try again later.';
        String errorCode = 'network_error';

        // Handle different Dio error types
        if (e.type == DioExceptionType.connectionTimeout ||
            e.type == DioExceptionType.sendTimeout ||
            e.type == DioExceptionType.receiveTimeout) {
          errorMessage =
              'Connection timed out. Please check your internet and try again.';
          errorCode = 'timeout';
        }

        lastException = EdgeFunctionException(
          code: errorCode,
          message: errorMessage,
          details: e.toString(),
        );

        retries++;
        if (retries <= _maxRetries) {
          await Future.delayed(Duration(seconds: retries * 2));
        }
      } on EdgeFunctionException catch (e) {
        _logger.e(
          'Edge function error in face comparison (attempt ${retries + 1}): ${e.toString()}',
        );
        lastException = e;
        retries++;

        if (retries <= _maxRetries) {
          await Future.delayed(Duration(seconds: retries * 2));
        }
      } catch (e) {
        _logger.e(
          'Unexpected error in face comparison (attempt ${retries + 1}): $e',
        );

        lastException = EdgeFunctionException(
          code: 'unknown_error',
          message: 'Failed to compare faces. Please try again later.',
          details: e.toString(),
        );

        retries++;
        if (retries <= _maxRetries) {
          await Future.delayed(Duration(seconds: retries * 2));
        }
      }
    }

    // If we get here, all retries failed
    _logger.e(
      'Face comparison failed after $_maxRetries retries: $lastException',
    );

    if (lastException is EdgeFunctionException) {
      throw lastException;
    } else {
      throw EdgeFunctionException(
        code: 'max_retries',
        message: 'Face comparison service unavailable. Please try again later.',
        details: lastException?.toString(),
      );
    }
  }
}
