import 'package:image_picker/image_picker.dart';

/// A model class that represents facial recognition data and metrics
class FaceModel {
  /// Path to the source image that contains the face
  final String imagePath;
  
  /// Unique identifier for the face
  final String faceId;
  
  /// Confidence score for face detection (0.0-1.0)
  final double confidence;
  
  /// Bounding box of face in the image
  final Map<String, double> boundingBox;
  
  /// Age range information
  final int? minAge;
  final int? maxAge;
  
  /// Gender information
  final String? gender;
  final double? genderConfidence;

  /// Facial expressions
  final bool? isSmiling;
  final double? smileConfidence;
  final bool? areEyesOpen;
  final double? eyesOpenConfidence;
  final bool? isMouthOpen;
  final double? mouthOpenConfidence;

  /// Accessories
  final bool? hasEyeglasses;
  final bool? hasSunglasses;
  final bool? hasBeard;
  final bool? hasMustache;

  /// Emotions (primary emotion)
  final String? primaryEmotion;
  final double? emotionConfidence;

  /// Face pose
  final double? roll;
  final double? yaw;
  final double? pitch;

  /// Image quality
  final double? brightness;
  final double? sharpness;

  /// Liveness detection data
  final bool isLive;
  final double livenessConfidence;
  
  /// Additional face details
  final Map<String, dynamic>? attributes;
  
  /// Message providing details about the face status
  final String message;

  const FaceModel({
    required this.imagePath,
    required this.faceId,
    this.confidence = 0.0,
    this.boundingBox = const {'x': 0.0, 'y': 0.0, 'width': 0.0, 'height': 0.0},
    this.minAge,
    this.maxAge,
    this.gender,
    this.genderConfidence,
    this.isSmiling,
    this.smileConfidence,
    this.areEyesOpen,
    this.eyesOpenConfidence,
    this.isMouthOpen,
    this.mouthOpenConfidence,
    this.hasEyeglasses,
    this.hasSunglasses,
    this.hasBeard,
    this.hasMustache,
    this.primaryEmotion,
    this.emotionConfidence,
    this.roll,
    this.yaw,
    this.pitch,
    this.brightness,
    this.sharpness,
    this.isLive = false,
    this.livenessConfidence = 0.0,
    this.attributes,
    this.message = '',
  });

  /// Constructor from edge function response based on Amazon Rekognition format
  factory FaceModel.fromEdgeFunction(
    XFile image,
    Map<String, dynamic> response,
  ) {
    // Check if we're parsing a face from the faceDetails array
    if (response.containsKey('BoundingBox')) {
      return _parseRekognitionFace(image, response);
    }
    
    // Check if we have success and faceDetails
    if (response.containsKey('success') &&
        response['success'] == true &&
        response.containsKey('faceDetails')) {
      final faceDetails = response['faceDetails'];
      if (faceDetails is List && faceDetails.isNotEmpty) {
        return _parseRekognitionFace(image, faceDetails[0]);
      }
    }
    
    // Fallback for unknown format
    return FaceModel(
      imagePath: image.path,
      faceId: 'face-${DateTime.now().millisecondsSinceEpoch}',
      confidence:
          response['Confidence'] != null
              ? (response['Confidence'] as num).toDouble() / 100.0
              : 0.0,
      boundingBox: {'x': 0.0, 'y': 0.0, 'width': 0.0, 'height': 0.0},
      message: 'Unknown face format',
    );
  }

  // Private helper to parse a Rekognition face detail
  static FaceModel _parseRekognitionFace(
    XFile image,
    Map<String, dynamic> faceData,
  ) {
    // Extract bounding box
    Map<String, double> boundingBox = {
      'x': 0.0,
      'y': 0.0,
      'width': 0.0,
      'height': 0.0,
    };
    if (faceData['BoundingBox'] != null) {
      final box = faceData['BoundingBox'];
      boundingBox = {
        'x': (box['Left'] ?? 0.0).toDouble(),
        'y': (box['Top'] ?? 0.0).toDouble(),
        'width': (box['Width'] ?? 0.0).toDouble(),
        'height': (box['Height'] ?? 0.0).toDouble(),
      };
    }
    
    // Extract age range
    int? minAge;
    int? maxAge;
    if (faceData['AgeRange'] != null) {
      minAge =
          faceData['AgeRange']['Low'] is num
              ? (faceData['AgeRange']['Low'] as num).toInt()
              : null;
      maxAge =
          faceData['AgeRange']['High'] is num
              ? (faceData['AgeRange']['High'] as num).toInt()
              : null;
    }
    
    // Extract gender
    String? gender;
    double? genderConfidence;
    if (faceData['Gender'] != null) {
      gender = faceData['Gender']['Value'];
      genderConfidence =
          faceData['Gender']['Confidence'] is num
              ? (faceData['Gender']['Confidence'] as num).toDouble() / 100.0
              : null;
    }

    // Extract expressions
    bool? isSmiling;
    double? smileConfidence;
    if (faceData['Smile'] != null) {
      isSmiling = faceData['Smile']['Value'];
      smileConfidence =
          faceData['Smile']['Confidence'] is num
              ? (faceData['Smile']['Confidence'] as num).toDouble() / 100.0
              : null;
    }

    bool? eyesOpen;
    double? eyesOpenConfidence;
    if (faceData['EyesOpen'] != null) {
      eyesOpen = faceData['EyesOpen']['Value'];
      eyesOpenConfidence =
          faceData['EyesOpen']['Confidence'] is num
              ? (faceData['EyesOpen']['Confidence'] as num).toDouble() / 100.0
              : null;
    }

    bool? mouthOpen;
    double? mouthOpenConfidence;
    if (faceData['MouthOpen'] != null) {
      mouthOpen = faceData['MouthOpen']['Value'];
      mouthOpenConfidence =
          faceData['MouthOpen']['Confidence'] is num
              ? (faceData['MouthOpen']['Confidence'] as num).toDouble() / 100.0
              : null;
    }

    // Extract accessories
    bool? hasEyeglasses;
    if (faceData['Eyeglasses'] != null) {
      hasEyeglasses = faceData['Eyeglasses']['Value'];
    }

    bool? hasSunglasses;
    if (faceData['Sunglasses'] != null) {
      hasSunglasses = faceData['Sunglasses']['Value'];
    }

    bool? hasBeard;
    if (faceData['Beard'] != null) {
      hasBeard = faceData['Beard']['Value'];
    }

    bool? hasMustache;
    if (faceData['Mustache'] != null) {
      hasMustache = faceData['Mustache']['Value'];
    }

    // Extract emotions
    String? primaryEmotion;
    double? emotionConfidence;
    if (faceData['Emotions'] != null &&
        faceData['Emotions'] is List &&
        (faceData['Emotions'] as List).isNotEmpty) {
      final topEmotion = faceData['Emotions'][0];
      primaryEmotion = topEmotion['Type'];
      emotionConfidence =
          topEmotion['Confidence'] is num
              ? (topEmotion['Confidence'] as num).toDouble() / 100.0
              : null;
    }

    // Extract pose
    double? roll;
    double? yaw;
    double? pitch;
    if (faceData['Pose'] != null) {
      roll =
          faceData['Pose']['Roll'] is num
              ? (faceData['Pose']['Roll'] as num).toDouble()
              : null;
      yaw =
          faceData['Pose']['Yaw'] is num
              ? (faceData['Pose']['Yaw'] as num).toDouble()
              : null;
      pitch =
          faceData['Pose']['Pitch'] is num
              ? (faceData['Pose']['Pitch'] as num).toDouble()
              : null;
    }

    // Extract quality
    double? brightness;
    double? sharpness;
    if (faceData['Quality'] != null) {
      brightness =
          faceData['Quality']['Brightness'] is num
              ? (faceData['Quality']['Brightness'] as num).toDouble()
              : null;
      sharpness =
          faceData['Quality']['Sharpness'] is num
              ? (faceData['Quality']['Sharpness'] as num).toDouble()
              : null;
    }
    
    // Get the confidence score
    double confidence =
        faceData['Confidence'] is num
            ? (faceData['Confidence'] as num).toDouble() / 100.0
            : 0.7;

    // Generate a unique face ID
    final faceId = 'face-${DateTime.now().millisecondsSinceEpoch}';

    // Create message about face quality
    String message = 'Face detected successfully';
    if (eyesOpen == true &&
        isSmiling == true &&
        sharpness != null &&
        sharpness > 80) {
      message = 'High quality face detected';
    }
    
    return FaceModel(
      imagePath: image.path,
      faceId: faceId,
      confidence: confidence,
      boundingBox: boundingBox,
      minAge: minAge,
      maxAge: maxAge,
      gender: gender,
      genderConfidence: genderConfidence,
      isSmiling: isSmiling,
      smileConfidence: smileConfidence,
      areEyesOpen: eyesOpen,
      eyesOpenConfidence: eyesOpenConfidence,
      isMouthOpen: mouthOpen,
      mouthOpenConfidence: mouthOpenConfidence,
      hasEyeglasses: hasEyeglasses,
      hasSunglasses: hasSunglasses,
      hasBeard: hasBeard,
      hasMustache: hasMustache,
      primaryEmotion: primaryEmotion,
      emotionConfidence: emotionConfidence,
      roll: roll,
      yaw: yaw,
      pitch: pitch,
      brightness: brightness,
      sharpness: sharpness,
      attributes: faceData,
      message: message,
    );
  }

  /// Creates an empty FaceModel with no data
  factory FaceModel.empty() {
    return const FaceModel(
      imagePath: '',
      faceId: '',
      message: 'No face data available',
    );
  }

  /// Creates a FaceModel with liveness details
  FaceModel withLiveness({
    required bool isLive,
    required double confidence,
    String? message,
  }) {
    return FaceModel(
      imagePath: imagePath,
      faceId: faceId,
      confidence: this.confidence,
      boundingBox: boundingBox,
      minAge: minAge,
      maxAge: maxAge,
      gender: gender,
      genderConfidence: genderConfidence,
      isLive: isLive,
      livenessConfidence: confidence,
      attributes: attributes,
      message: message ?? this.message,
    );
  }

  /// Updates the message for this FaceModel
  FaceModel withMessage(String newMessage) {
    return FaceModel(
      imagePath: imagePath,
      faceId: faceId,
      confidence: confidence,
      boundingBox: boundingBox,
      minAge: minAge,
      maxAge: maxAge,
      gender: gender,
      genderConfidence: genderConfidence,
      isLive: isLive,
      livenessConfidence: livenessConfidence,
      attributes: attributes,
      message: newMessage,
    );
  }

  /// Checks if this FaceModel instance has valid face data
  bool get hasValidFace => faceId.isNotEmpty && confidence > 0.5;

  /// Returns a JSON representation of this model
  Map<String, dynamic> toJson() {
    return {
      'imagePath': imagePath,
      'faceId': faceId,
      'confidence': confidence,
      'boundingBox': boundingBox,
      'minAge': minAge,
      'maxAge': maxAge,
      'gender': gender,
      'genderConfidence': genderConfidence,
      'isSmiling': isSmiling,
      'smileConfidence': smileConfidence,
      'areEyesOpen': areEyesOpen,
      'eyesOpenConfidence': eyesOpenConfidence,
      'isMouthOpen': isMouthOpen,
      'mouthOpenConfidence': mouthOpenConfidence,
      'hasEyeglasses': hasEyeglasses,
      'hasSunglasses': hasSunglasses,
      'hasBeard': hasBeard,
      'hasMustache': hasMustache,
      'primaryEmotion': primaryEmotion,
      'emotionConfidence': emotionConfidence,
      'roll': roll,
      'yaw': yaw,
      'pitch': pitch,
      'brightness': brightness,
      'sharpness': sharpness,
      'isLive': isLive,
      'livenessConfidence': livenessConfidence,
      'attributes': attributes,
      'message': message,
    };
  }

  /// Creates a FaceModel from JSON data
  factory FaceModel.fromJson(Map<String, dynamic> json) {
    return FaceModel(
      imagePath: json['imagePath'] as String? ?? '',
      faceId: json['faceId'] as String? ?? '',
      confidence: (json['confidence'] as num?)?.toDouble() ?? 0.0,
      boundingBox:
          json['boundingBox'] != null
              ? Map<String, double>.from(json['boundingBox'])
              : const {'x': 0.0, 'y': 0.0, 'width': 0.0, 'height': 0.0},
      minAge: json['minAge'] as int?,
      maxAge: json['maxAge'] as int?,
      gender: json['gender'] as String?,
      genderConfidence: (json['genderConfidence'] as num?)?.toDouble(),
      isSmiling: json['isSmiling'] as bool?,
      smileConfidence: (json['smileConfidence'] as num?)?.toDouble(),
      areEyesOpen: json['areEyesOpen'] as bool?,
      eyesOpenConfidence: (json['eyesOpenConfidence'] as num?)?.toDouble(),
      isMouthOpen: json['isMouthOpen'] as bool?,
      mouthOpenConfidence: (json['mouthOpenConfidence'] as num?)?.toDouble(),
      hasEyeglasses: json['hasEyeglasses'] as bool?,
      hasSunglasses: json['hasSunglasses'] as bool?,
      hasBeard: json['hasBeard'] as bool?,
      hasMustache: json['hasMustache'] as bool?,
      primaryEmotion: json['primaryEmotion'] as String?,
      emotionConfidence: (json['emotionConfidence'] as num?)?.toDouble(),
      roll: (json['roll'] as num?)?.toDouble(),
      yaw: (json['yaw'] as num?)?.toDouble(),
      pitch: (json['pitch'] as num?)?.toDouble(),
      brightness: (json['brightness'] as num?)?.toDouble(),
      sharpness: (json['sharpness'] as num?)?.toDouble(),
      isLive: json['isLive'] as bool? ?? false,
      livenessConfidence:
          (json['livenessConfidence'] as num?)?.toDouble() ?? 0.0,
      attributes: json['attributes'] as Map<String, dynamic>?,
      message: json['message'] as String? ?? '',
    );
  }

  /// Returns a map representation of this model
  Map<String, dynamic> toMap() {
    return {
      'imagePath': imagePath,
      'faceId': faceId,
      'confidence': confidence,
      'boundingBox': boundingBox,
      'minAge': minAge,
      'maxAge': maxAge,
      'gender': gender,
      'genderConfidence': genderConfidence,
      'isSmiling': isSmiling,
      'smileConfidence': smileConfidence,
      'areEyesOpen': areEyesOpen,
      'eyesOpenConfidence': eyesOpenConfidence,
      'isMouthOpen': isMouthOpen,
      'mouthOpenConfidence': mouthOpenConfidence,
      'hasEyeglasses': hasEyeglasses,
      'hasSunglasses': hasSunglasses,
      'hasBeard': hasBeard,
      'hasMustache': hasMustache,
      'primaryEmotion': primaryEmotion,
      'emotionConfidence': emotionConfidence,
      'roll': roll,
      'yaw': yaw,
      'pitch': pitch,
      'brightness': brightness,
      'sharpness': sharpness,
      'isLive': isLive,
      'livenessConfidence': livenessConfidence,
      'message': message,
      'hasValidFace': hasValidFace,
    };
  }
}

/// Result of a face comparison between two faces
class FaceComparisonResult {
  /// The source face in the comparison
  final FaceModel sourceFace;

  /// The target face in the comparison
  final FaceModel targetFace;

  /// Whether the faces match
  final bool isMatch;

  /// Confidence level of the match (0.0-1.0)
  final double confidence;
  
  /// Similarity score (0-100)
  final double similarity;

  /// Threshold used for matching
  final double similarityThreshold;

  /// Confidence level as text (HIGH, MEDIUM, LOW)
  final String? confidenceLevel;

  /// Message describing the comparison result
  final String message;

  /// Creates a FaceComparisonResult with the given parameters
  const FaceComparisonResult({
    required this.sourceFace,
    required this.targetFace,
    required this.isMatch,
    required this.confidence,
    this.similarity = 0.0,
    this.similarityThreshold = 0.0,
    this.confidenceLevel,
    required this.message,
  });

  /// Factory constructor for Edge Function comparison response
  factory FaceComparisonResult.fromEdgeFunction(
    FaceModel sourceFace,
    FaceModel targetFace,
    Map<String, dynamic> response,
  ) {
    // Check if the response is valid
    if (!response.containsKey('success') || response['success'] != true) {
      return FaceComparisonResult.error(
        sourceFace,
        targetFace,
        'Invalid or failed response',
      );
    }

    // Extract match result
    final bool isMatch = response['matched'] ?? false;

    // Extract similarity and threshold
    final double similarity = (response['similarity'] ?? 0.0).toDouble();
    final double similarityThreshold =
        (response['similarityThreshold'] ?? 0.0).toDouble();

    // Calculate normalized confidence (0.0-1.0)
    final double confidence = similarity / 100.0;

    // Get confidence level if available
    final String? confidenceLevel = response['confidence'];
    
    // Generate appropriate message
    String message;
    if (isMatch) {
      message = 'Faces match with ${similarity.toStringAsFixed(1)}% similarity';
      if (confidenceLevel != null) {
        message += ' ($confidenceLevel confidence)';
      }
    } else {
      message =
          'Faces do not match. Similarity: ${similarity.toStringAsFixed(1)}%';
      if (similarityThreshold > 0) {
        message += ' (threshold: ${similarityThreshold.toStringAsFixed(1)}%)';
      }
    }
    
    return FaceComparisonResult(
      sourceFace: sourceFace,
      targetFace: targetFace,
      isMatch: isMatch,
      confidence: confidence,
      similarity: similarity,
      similarityThreshold: similarityThreshold,
      confidenceLevel: confidenceLevel,
      message: message,
    );
  }

  /// Creates an empty result with no match
  factory FaceComparisonResult.noMatch(
    FaceModel sourceFace,
    FaceModel targetFace, {
    String? message,
  }) {
    return FaceComparisonResult(
      sourceFace: sourceFace,
      targetFace: targetFace,
      isMatch: false,
      confidence: 0.0,
      message: message ?? 'No matching faces',
    );
  }

  /// Creates an error result
  factory FaceComparisonResult.error(
    FaceModel sourceFace,
    FaceModel targetFace,
    String errorMessage,
  ) {
    return FaceComparisonResult(
      sourceFace: sourceFace,
      targetFace: targetFace,
      isMatch: false,
      confidence: 0.0,
      message: 'Error: $errorMessage',
    );
  }

  /// Returns a JSON representation of this result
  Map<String, dynamic> toJson() {
    return {
      'sourceFace': sourceFace.toJson(),
      'targetFace': targetFace.toJson(),
      'isMatch': isMatch,
      'confidence': confidence,
      'similarity': similarity,
      'similarityThreshold': similarityThreshold,
      'confidenceLevel': confidenceLevel,
      'message': message,
    };
  }

  /// Creates a FaceComparisonResult from JSON data
  factory FaceComparisonResult.fromJson(Map<String, dynamic> json) {
    return FaceComparisonResult(
      sourceFace: FaceModel.fromJson(json['sourceFace'] ?? {}),
      targetFace: FaceModel.fromJson(json['targetFace'] ?? {}),
      isMatch: json['isMatch'] as bool? ?? false,
      confidence: (json['confidence'] as num?)?.toDouble() ?? 0.0,
      similarity: (json['similarity'] as num?)?.toDouble() ?? 0.0,
      similarityThreshold:
          (json['similarityThreshold'] as num?)?.toDouble() ?? 0.0,
      confidenceLevel: json['confidenceLevel'] as String?,
      message: json['message'] as String? ?? '',
    );
  }
}
