import base64
import numpy as np
import cv2
import mediapipe as mp
import numpy as np
from asyncpg.pool import create_pool
import os
from dotenv import load_dotenv

load_dotenv()
mp_face_mesh = mp.solutions.face_mesh
face_mesh = mp_face_mesh.FaceMesh(
    min_detection_confidence=0.7, 
    min_tracking_confidence=0.7,
    refine_landmarks=True
)

previous_face_center = None
previous_pupil_positions = None


DATABASE_URL = "postgresql://neondb_owner:npg_N0ieunV7fAqQ@ep-gentle-lake-a8g16vae-pooler.eastus2.azure.neon.tech/neondb?sslmode=require"
pool = None
auth = False
conn = None

async def get_auth(api_key):
    global auth
    global pool
    global conn
    
    async def get_db_connection():
        return await pool.acquire()
    
        
    try:
        if pool is None:
            pool = await create_pool(DATABASE_URL)
            
        conn = await get_db_connection()

        query = '''
            SELECT email, username 
            FROM proctor_users 
            WHERE api_key = $1
        '''
        result = await conn.fetchrow(query, api_key)
        if result:
            auth = True
            return {
                "auth_status": "Authentication Success",
                "email": result["email"],
                "name": result["username"]
            }
        else:
            raise Exception("Authentication Failed: API Key not found")
    except Exception as e:
        return {"auth_status": "Authentication Failed", "error": str(e)}

        

        

async def logout():
    global auth
    if auth is False:
        return {"error": "Authentication required. Please log in first."}
    auth = False
    if pool is not None:
        await pool.close()
    return {"message": "Logout successful. Authentication status reset."}




def calculate_distance(p1, p2):
    """Calculate Euclidean distance between two points"""
    return np.linalg.norm(np.array(p1) - np.array(p2))

def eye_aspect_ratio(eye):
    """Calculate the Eye Aspect Ratio (EAR) for eye openness detection"""
    if len(eye) < 6:
        return 0
    vertical_1 = calculate_distance(eye[1], eye[5])  
    vertical_2 = calculate_distance(eye[2], eye[4])  
    horizontal = calculate_distance(eye[0], eye[3])  
    return (vertical_1 + vertical_2) / (2.0 * horizontal) if horizontal > 0 else 0


def detect_frame(frame_base64):
    """
    Detects anomalies in the given frame.

    Args:
        frame_base64 (str): Base64-encoded image.

    Returns:
        dict: Anomaly detection results.
    """
    global previous_face_center, previous_pupil_positions, auth, pool

    try:
        
        if not auth:
            raise Exception("Unauthorized access")

        if pool is None:
            raise Exception("Database connection not established")
        
        img_data = base64.b64decode(frame_base64)
        img_array = np.frombuffer(img_data, dtype=np.uint8)
        img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)

        if img is None:
            return {"error": "Invalid image"}

        img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        results = face_mesh.process(img_rgb)

        anomaly_detected = False
        pupil_movement_status = "straight"

        if results.multi_face_landmarks:
            if len(results.multi_face_landmarks) > 1:
                anomaly_detected = True

            for face_landmarks in results.multi_face_landmarks:
                try:
                    landmarks = face_landmarks.landmark
                    if len(landmarks) < 474:
                        continue

                    left_eye = [(landmarks[i].x, landmarks[i].y) for i in [33, 159, 158, 133, 153, 145]]
                    right_eye = [(landmarks[i].x, landmarks[i].y) for i in [362, 386, 387, 463, 373, 380]]

                    left_ear = eye_aspect_ratio(left_eye)
                    right_ear = eye_aspect_ratio(right_eye)

                    if left_ear < 0.18 or right_ear < 0.18:
                        anomaly_detected = True

                    face_left = (landmarks[234].x, landmarks[234].y)
                    face_right = (landmarks[454].x, landmarks[454].y)
                    face_width = calculate_distance(face_left, face_right)

                    face_center = np.mean([(lm.x, lm.y) for lm in landmarks], axis=0)

                    if previous_face_center is not None:
                        face_movement = calculate_distance(previous_face_center, face_center)
                        if face_movement > 0.05:
                            anomaly_detected = True

                    previous_face_center = face_center

                    pupil_positions = {
                        "left": (landmarks[468].x, landmarks[468].y),
                        "right": (landmarks[473].x, landmarks[473].y)
                    }

                    if previous_pupil_positions:
                        left_pupil_movement = calculate_distance(previous_pupil_positions["left"], pupil_positions["left"])
                        right_pupil_movement = calculate_distance(previous_pupil_positions["right"], pupil_positions["right"])
                        avg_pupil_movement = (left_pupil_movement + right_pupil_movement) / 2

                        if avg_pupil_movement < 0.002:
                            pupil_movement_status = "straight"
                        elif avg_pupil_movement < 0.01:
                            pupil_movement_status = "usual"
                        else:
                            pupil_movement_status = "unusual"

                    previous_pupil_positions = pupil_positions  

                except Exception as e:
                    print(f"Error processing face landmarks: {e}")
                    anomaly_detected = True

        else:
            anomaly_detected = True

        anomaly_frame_base64 = None
        if anomaly_detected:
            _, buffer = cv2.imencode('.jpg', img)
            anomaly_frame_base64 = base64.b64encode(buffer).decode('utf-8')

        return {
            'anomaly': anomaly_detected,
            'pupil_movement_status': pupil_movement_status,
            'frame': anomaly_frame_base64
        }

    except Exception as e:
        return {"error": f"Unexpected error: {e}"}
