from flask import Flask, request, jsonify
from flask_cors import CORS
import face_recognition
import numpy as np
import base64
import cv2
import os

app = Flask(__name__)
CORS(app)  # Enable CORS for all routes

# In-memory storage for known face encodings and names
known_face_encodings = []
known_face_names = []
known_face_ids = []

def load_known_faces():
    """
    Load known faces from a directory or database.
    For this MVP, we'll assume images are stored in a 'faces' directory 
    with filenames like 'partner_id_name.jpg'.
    In a real production scenario, you'd fetch these from Odoo or a shared storage.
    """
    global known_face_encodings, known_face_names, known_face_ids
    
    faces_dir = "faces"
    if not os.path.exists(faces_dir):
        os.makedirs(faces_dir)
        print(f"Created {faces_dir} directory. Please add images there.")
        return

    for filename in os.listdir(faces_dir):
        if filename.endswith((".jpg", ".png", ".jpeg")):
            try:
                # Filename format: ID_Name.jpg
                name_part = os.path.splitext(filename)[0]
                parts = name_part.split('_', 1)
                if len(parts) == 2:
                    p_id = int(parts[0])
                    name = parts[1]
                else:
                    p_id = 0
                    name = name_part

                image_path = os.path.join(faces_dir, filename)
                image = face_recognition.load_image_file(image_path)
                encodings = face_recognition.face_encodings(image)

                if encodings:
                    known_face_encodings.append(encodings[0])
                    known_face_names.append(name)
                    known_face_ids.append(p_id)
                    print(f"Loaded face for: {name} (ID: {p_id})")
            except Exception as e:
                print(f"Error loading {filename}: {e}")

@app.route('/recognize', methods=['POST'])
def recognize_face():
    data = request.json
    if 'image' not in data:
        return jsonify({'error': 'No image provided'}), 400

    try:
        # Decode base64 image
        image_data = base64.b64decode(data['image'])
        nparr = np.frombuffer(image_data, np.uint8)
        frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
        
        if frame is None:
            return jsonify({'error': 'Invalid image data'}), 400
        
        # Convert BGR (OpenCV) to RGB (face_recognition)
        rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        
        # Ensure the image is in the correct format
        rgb_frame = np.ascontiguousarray(rgb_frame, dtype=np.uint8)

        # Find all faces in the current frame
        face_locations = face_recognition.face_locations(rgb_frame)
        face_encodings = face_recognition.face_encodings(rgb_frame, face_locations)

        matches_list = []

        for face_encoding in face_encodings:
            # Use threshold of 0.4 for slightly less strict matching (around 60% match)
            matches = face_recognition.compare_faces(known_face_encodings, face_encoding, tolerance=0.4)
            name = "Unknown"
            p_id = 0
            probability = 0.0

            # Or instead, use the known face with the smallest distance to the new face
            face_distances = face_recognition.face_distance(known_face_encodings, face_encoding)
            if len(face_distances) > 0:
                best_match_index = np.argmin(face_distances)
                if matches[best_match_index]:
                    name = known_face_names[best_match_index]
                    p_id = known_face_ids[best_match_index]
                    # Simple probability estimation based on distance (lower distance = higher prob)
                    # Distance 0.0 -> 100%, Distance 0.3 -> 70%
                    distance = face_distances[best_match_index]
                    probability = max(0, 1.0 - distance)
                    
                    print(f"Best match for current face: {name} (ID: {p_id}) with distance {distance:.3f} (Prob: {probability:.3f})")

                    if probability >= 0.6:
                        matches_list.append({
                            'id': p_id,
                            'name': name,
                            'probability': probability
                        })

        # Sort by probability
        matches_list.sort(key=lambda x: x['probability'], reverse=True)

        return jsonify({'matches': matches_list})

    except Exception as e:
        print(f"Error processing image: {e}")
        return jsonify({'error': str(e)}), 500

@app.route('/train', methods=['POST'])
def train_face():
    data = request.json
    if 'partner_id' not in data or 'images' not in data:
        return jsonify({'error': 'Missing partner_id or images'}), 400

    partner_id = data['partner_id']
    name = data.get('name', 'Unknown')
    images = data['images'] # List of base64 strings

    faces_dir = "faces"
    if not os.path.exists(faces_dir):
        os.makedirs(faces_dir)

    processed_count = 0
    
    # Clear existing images for this partner to avoid duplicates/stale data?
    # For simplicity, we might just overwrite or append. Let's append with index.
    # Or better, delete old ones first if we want to keep it clean.
    # Let's just save new ones for now.
    
    for idx, img_data in enumerate(images):
        if not img_data:
            continue
            
        try:
            # Decode base64
            image_bytes = base64.b64decode(img_data)
            filename = f"{partner_id}_{name.replace(' ', '_')}_{idx}.jpg"
            filepath = os.path.join(faces_dir, filename)
            
            with open(filepath, "wb") as f:
                f.write(image_bytes)
            
            # Update in-memory knowledge
            image = face_recognition.load_image_file(filepath)
            encodings = face_recognition.face_encodings(image)
            
            if encodings:
                known_face_encodings.append(encodings[0])
                known_face_names.append(name)
                known_face_ids.append(partner_id)
                processed_count += 1
                print(f"Trained face for: {name} (ID: {partner_id}) - Image {idx}")

        except Exception as e:
            print(f"Error saving/training image {idx} for {name}: {e}")

    return jsonify({'message': f'Processed {processed_count} images for {name}'})

load_known_faces()

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
