import smtplib
import csv
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

import os
import sys
from flask import Flask, request, jsonify, make_response
from flask_cors import CORS
import base64
import cv2
import numpy as np
import json
import datetime
import requests
import urllib3

import secrets
import time

# Add the project root to the Python path
# This allows us to run the server directly while maintaining package imports
current_dir = os.path.dirname(os.path.abspath(__file__))
# Assuming 'franai' is the package directory, its parent is the project root.
project_root = os.path.dirname(current_dir)
if project_root not in sys.path:
    sys.path.append(project_root)

# Disable SSL warnings for API calls
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
from franai.livestream import train_face_recognizer, preprocess_face
from franai.controllers.auth_controller import auth_bp
from franai.controllers.access_control import role_required
from franai.models.token_model import RegistrationToken
from franai.models.role_model import Role
from franai.models.registered_user_model import RegisteredUser
import mysql.connector
from franai.services.config import Config

# Ensure the tables exist on startup
RegistrationToken.create_table_if_not_exists()
Role.create_tables_if_not_exists()
RegisteredUser.create_table_if_not_exists()

def get_team_gid(pid):
    """Connects to the 'intra' database to get the team GID for a given PID."""
    team_gid = None
    # Use a separate connection for the 'intra' database
    try:
        conn = mysql.connector.connect(
            host=Config.DB_HOST,
            port=Config.DB_PORT,
            user=Config.DB_USER,
            password=Config.DB_PASSWORD,
            database='intra'  # Explicitly connect to the 'intra' database
        )
        if conn.is_connected():
            cursor = conn.cursor(dictionary=True)
            query = """
            SELECT tg.group_id 
            FROM tag_group tg
            JOIN user_group ug ON tg.group_id = ug.GID
            WHERE tg.user_id = %s 
            AND tg.workspace_default = 'Y'
            AND ug.workspace_status = 1
            LIMIT 1
            """
            cursor.execute(query, (pid,))
            result = cursor.fetchone()
            if result:
                team_gid = result['group_id']
    except mysql.connector.Error as e:
        print(f"Error connecting to 'intra' database or fetching team GID: {e}")
        return None # Return None on error
    finally:
        if 'conn' in locals() and conn.is_connected():
            cursor.close()
            conn.close()
    return team_gid

# InsightFace imports with error handling
try:
    import insightface
    import onnxruntime
    import pickle
    INSIGHTFACE_AVAILABLE = True
    print("✅ InsightFace libraries loaded successfully")
except ImportError as e:
    INSIGHTFACE_AVAILABLE = False
    print(f"⚠️ InsightFace not available: {e}")
    print("📝 System will continue with OpenCV only")

from franai.models.staff_model import Staff
from franai.models.team_model import Team

app = Flask(__name__)
# Register Blueprints
app.register_blueprint(auth_bp, url_prefix='/auth')

# --- SMTP Email Configuration ---
# IMPORTANT: For production, use environment variables.
app.config['SMTP_SERVER'] = os.environ.get('SMTP_SERVER', 'smtp.example.com')
app.config['SMTP_PORT'] = int(os.environ.get('SMTP_PORT', 587))
app.config['SMTP_USERNAME'] = os.environ.get('SMTP_USERNAME', 'user@example.com')
app.config['SMTP_PASSWORD'] = os.environ.get('SMTP_PASSWORD', 'password')
app.config['SENDER_EMAIL'] = os.environ.get('SENDER_EMAIL', 'noreply@example.com')
app.config['FRONTEND_URL'] = os.environ.get('FRONTEND_URL', 'http://localhost:5173')


@app.route('/api/teams', methods=['GET'])
def get_teams():
    """Get a list of all active teams."""
    teams = Team.get_all_teams()
    if teams is None:
        return jsonify({'error': 'An error occurred while fetching teams.'}), 500
    return jsonify(teams)


@app.route('/api/staff/search', methods=['GET'])
def search_staff():
    """Search for staff by name or get a default list, optionally filtered by team."""
    search_term = request.args.get('name', '')
    team_gid = request.args.get('team_gid', None)
    
    if not search_term:
        # If no search term, get a default list of users
        users = Staff.get_all_staff(team_gid=team_gid)
    else:
        # Otherwise, search by the provided name
        users = Staff.search_by_name(search_term, team_gid=team_gid)

    if users is None:
        # search_by_name can return None on error, get_all_staff returns []
        return jsonify({'error': 'An error occurred while fetching users.'}), 500
    
    return jsonify(users)

@app.route('/api/tokens/active', methods=['GET'])
@role_required('Super Admin')
def get_active_tokens(current_user_pid):
    """Get a list of all active (non-expired) registration tokens."""
    tokens = RegistrationToken.get_active_tokens()
    if tokens is None:
        return jsonify({'error': 'An error occurred while fetching active tokens.'}), 500
    
    # Convert datetime objects to ISO 8601 string format for JSON serialization
    for token in tokens:
        if isinstance(token.get('expires_at'), datetime.datetime):
            token['expires_at'] = token['expires_at'].isoformat()
            
    return jsonify(tokens)

@app.route('/api/tokens', methods=['GET'])
@role_required('Super Admin')
def get_all_tokens(current_user_pid):
    """Get a list of all registration tokens, including expired ones."""
    tokens = RegistrationToken.get_all_tokens()
    if tokens is None:
        return jsonify({'error': 'An error occurred while fetching all tokens.'}), 500
    
    # Convert datetime objects to ISO 8601 string format for JSON serialization
    for token in tokens:
        if isinstance(token.get('expires_at'), datetime.datetime):
            token['expires_at'] = token['expires_at'].isoformat()
            
    return jsonify(tokens)

@app.route('/admin/dashboard')
@role_required('Admin', 'Super Admin')
def admin_dashboard(current_user_pid):
    """A sample protected route that requires Admin or Super Admin role."""
    return jsonify(message=f"Welcome to the Admin Dashboard, user PID: {current_user_pid}!")

@app.route('/admin/generate-and-email-token', methods=['POST'])
@role_required('Super Admin') # Secure this endpoint
def generate_and_email_token(current_user_pid):
    """Generates a one-time code and emails a registration link to the staff member."""
    data = request.get_json()
    pid = data.get('pid')
    if not pid:
        return jsonify({'error': 'PID is required'}), 400

    # 1. Fetch employee info to get name and email
    employee_info = fetch_employee_info(pid)
    if not employee_info or not employee_info.get('email'):
        return jsonify({'error': f'Could not find a valid email for User ID {pid}.'}), 404
    
    employee_name = employee_info.get('name', 'Employee')
    employee_email = employee_info.get('email')

    # 2. Get the team GID
    team_gid = get_team_gid(pid)

    # 3. Generate and store a secure token in the database
    code = RegistrationToken.create(pid, team_gid)
    if not code:
        return jsonify({'error': 'Failed to generate a secure token.'}), 500
    
    # 4. Construct the registration link
    registration_link = f"{app.config['FRONTEND_URL']}/self-register?code={code}"

    # 5. Send the email
    try:
        msg = MIMEMultipart()
        msg['From'] = app.config['SENDER_EMAIL']
        msg['To'] = employee_email
        msg['Subject'] = "Your Secure Face Recognition Registration Link"

        body = f"""
        Hello {employee_name},

        Please use the following secure link to register for the Face Recognition Attendance System.

        Registration Link: {registration_link}

        Please note: This link is valid for 5 minutes and can only be used once.

        If you did not request this, please ignore this email.

        Thank you,
        The Admin Team
        """
        msg.attach(MIMEText(body, 'plain'))

        server = smtplib.SMTP(app.config['SMTP_SERVER'], app.config['SMTP_PORT'])
        server.starttls()
        server.login(app.config['SMTP_USERNAME'], app.config['SMTP_PASSWORD'])
        text = msg.as_string()
        server.sendmail(app.config['SENDER_EMAIL'], employee_email, text)
        server.quit()

        print(f"Successfully sent registration email to {employee_email} for PID {pid}")
        return jsonify({'success': True, 'message': f'Registration link sent to {employee_name} ({employee_email}).'})

    except Exception as e:
        print(f"ERROR: Failed to send email: {e}")
        # Clean up the generated code if email fails
        RegistrationToken.delete(code) # Clean up from DB
        return jsonify({'error': 'Failed to send registration email. Please check server configuration.'}), 500

@app.route('/admin/send-token-email', methods=['POST'])
@role_required('Super Admin')
def send_token_email(current_user_pid):
    """Generates a token and emails it to the user's official email."""
    data = request.get_json()
    pid = data.get('pid')
    if not pid:
        return jsonify({'error': 'PID is required'}), 400

    employee_info = fetch_employee_info(pid)
    if not employee_info or not employee_info.get('email'):
        return jsonify({'error': f'Could not find a valid email for User ID {pid}.'}), 404
    
    employee_name = employee_info.get('name', 'Employee')
    employee_email = employee_info.get('email')
    team_gid = get_team_gid(pid)
    code = RegistrationToken.create(pid, team_gid)
    if not code:
        return jsonify({'error': 'Failed to generate a secure token.'}), 500
    
    registration_link = f"{app.config['FRONTEND_URL']}/self-register?code={code}"

    token_data = RegistrationToken.find_by_code(code)
    if not token_data:
        return jsonify({'error': 'Failed to retrieve token details after creation.'}), 500

    expires_at_dt = token_data['expires_at']
    
    # Format the expiration date and time for the email body
    # Example: "October 01, 2025 at 03:30 PM"
    expires_at_formatted = expires_at_dt.strftime("%B %d, %Y at %I:%M %p")

    try:
        msg = MIMEMultipart()
        msg['From'] = app.config['SENDER_EMAIL']
        msg['To'] = employee_email
        msg['Subject'] = "Your Secure Face Recognition Registration Link"
        body = f"""Hello {employee_name},

Please use the following secure link to register for the Face Recognition Attendance System.

Registration Link: {registration_link}

Please note: This link will expire on {expires_at_formatted}.

If you did not request this, please ignore this email.

Thank you,
The Admin Team"""
        msg.attach(MIMEText(body, 'plain'))
        server = smtplib.SMTP(app.config['SMTP_SERVER'], app.config['SMTP_PORT'])
        server.starttls()
        server.login(app.config['SMTP_USERNAME'], app.config['SMTP_PASSWORD'])
        text = msg.as_string()
        server.sendmail(app.config['SENDER_EMAIL'], employee_email, text)
        server.quit()
        
        # Mark the token as sent in the database
        RegistrationToken.mark_as_sent(code)
        
        return jsonify({'success': True, 'message': f'Registration link sent to {employee_name} ({employee_email}).'})
    except Exception as e:
        print(f"ERROR: Failed to send email: {e}")
        RegistrationToken.delete(code)
        return jsonify({'error': 'Failed to send registration email.'}), 500

@app.route('/admin/generate-token', methods=['POST'])
@role_required('Super Admin')
def generate_token(current_user_pid):
    """Generates a one-time code and returns a registration link."""
    data = request.get_json()
    pid = data.get('pid')
    if not pid:
        return jsonify({'error': 'PID is required'}), 400

    # Get the team GID from the 'intra' database
    team_gid = get_team_gid(pid)

    # Generate and store a secure token in the database, now including the team_gid
    code = RegistrationToken.create(pid, team_gid)
    if not code:
        return jsonify({'error': 'Failed to generate a secure token.'}), 500
    
    # Construct the registration link
    registration_link = f"{app.config['FRONTEND_URL']}/self-register?code={code}"

    return jsonify({'success': True, 'link': registration_link})


# CORS policy for Fran
CORS(app, 
     origins=['https://fran-dev.scrubbed.net', 'https://fran.scrubbed.net', 'http://localhost:8081', 'http://localhost:3000', 'http://localhost:5173', 'http://localhost:5174', 'https://api-frandev.scrubbed.net'],
     supports_credentials=True,
     methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
     allow_headers=['Content-Type', 'x-access-token', 'Authorization'])
FACE_DIR = "registered_faces"
EMBEDDINGS_DIR = "insightface_embeddings"  # Directory for InsightFace embeddings
API_ENDPOINT = "https://api-hris.scrubbed.net/users/getUserInfo"
SETTINGS_FILE = "app_settings.json"

# Ensure the embeddings directory exists
os.makedirs(EMBEDDINGS_DIR, exist_ok=True)

# Global InsightFace model (initialized once)
insightface_model = None

# Default settings
DEFAULT_SETTINGS = {
    "recognition_library": "insightface",  # Default to InsightFace
    "insightface_threshold": 0.6,
    "opencv_threshold": 100
}

def load_settings():
    """Load application settings from file"""
    try:
        if os.path.exists(SETTINGS_FILE):
            with open(SETTINGS_FILE, 'r') as f:
                settings = json.load(f)
                # Merge with defaults to ensure all keys exist
                for key, value in DEFAULT_SETTINGS.items():
                    if key not in settings:
                        settings[key] = value
                return settings
        else:
            return DEFAULT_SETTINGS.copy()
    except Exception as e:
        print(f"⚠️ Error loading settings: {e}")
        return DEFAULT_SETTINGS.copy()

def save_settings(settings):
    """Save application settings to file"""
    try:
        with open(SETTINGS_FILE, 'w') as f:
            json.dump(settings, f, indent=2)
        print(f"✅ Settings saved: {settings}")
        return True
    except Exception as e:
        print(f"❌ Error saving settings: {e}")
        return False

def initialize_insightface():
    """Initialize InsightFace model globally with optimized providers."""
    global insightface_model
    if not INSIGHTFACE_AVAILABLE:
        return False
    
    try:
        if insightface_model is None:
            # Prioritize GPU providers if available, otherwise fall back to CPU
            providers = ['CUDAExecutionProvider', 'TensorrtExecutionProvider', 'CPUExecutionProvider']
            
            insightface_model = insightface.app.FaceAnalysis(providers=providers)
            insightface_model.prepare(ctx_id=0, det_size=(640, 640))
            
            # Log the provider being used
            print(f"✅ InsightFace model initialized successfully using provider: {insightface_model.models['detection'].session.get_providers()}")
        return True
    except Exception as e:
        print(f"❌ Error initializing InsightFace model: {e}")
        return False

def generate_insightface_embeddings(user_id, user_folder, user_name, user_department):
    """Generate InsightFace embeddings for user images"""
    if not INSIGHTFACE_AVAILABLE:
        print("⚠️ InsightFace not available, skipping embedding generation")
        return False
    
    try:
        # Initialize model if not already done
        if not initialize_insightface():
            return False
        
        embeddings = []
        image_files = [f for f in os.listdir(user_folder) if f.endswith('.jpg')]
        
        print(f"🔄 Processing {len(image_files)} images for InsightFace embeddings...")
        
        for img_file in image_files:
            img_path = os.path.join(user_folder, img_file)
            img = cv2.imread(img_path)
            
            if img is not None:
                faces = insightface_model.get(img)
                if faces:
                    # Use first detected face
                    embedding = faces[0].embedding
                    embeddings.append(embedding)
                    print(f"✅ Generated embedding for {img_file}")
                else:
                    print(f"⚠️ No face detected in {img_file}")
            else:
                print(f"❌ Could not read image {img_file}")
        
        if embeddings:
            # Save embeddings to pickle file
            embedding_data = {
                'user_id': user_id,
                'name': user_name,
                'department': user_department,
                'embeddings': embeddings,
                'created_at': datetime.datetime.now().isoformat(),
                'image_count': len(embeddings)
            }
            
            embedding_file = os.path.join(EMBEDDINGS_DIR, f"insightface_embeddings_{user_id}.pkl")
            with open(embedding_file, 'wb') as f:
                pickle.dump(embedding_data, f)
            
            print(f"✅ Generated {len(embeddings)} InsightFace embeddings for user {user_id}")
            print(f"💾 Saved embeddings to {embedding_file}")
            return True
        else:
            print(f"⚠️ No faces detected in any images for user {user_id}")
            return False
            
    except Exception as e:
        print(f"❌ Error generating InsightFace embeddings: {e}")
        return False

def fetch_employee_info(user_id):
    """Fetch employee information from live API endpoint"""
    try:
        # Use GET request with user_id in URL path
        url = f"{API_ENDPOINT}/{user_id}"
        headers = {"Content-Type": "application/json"}
        
        response = requests.get(url, headers=headers, timeout=10, verify=False)
        
        if response.status_code == 200:
            # Parse the response to extract employee information
            response_data = response.json()
            
            # Extract employee details from API response
            employee_info = {
                'user_id': str(user_id),
                'name': response_data.get('name', f'User {user_id}'),
                'email': response_data.get('email', ''),
                'department': response_data.get('department', ''),
                'status': response_data.get('status', 'active'),
                'api_response': response_data  # Keep full response for debugging
            }
            
            print(f"✅ Fetched employee info for User {user_id}: {employee_info['name']} - {employee_info['department']}")
            return employee_info
            
        else:
            print(f"⚠️ API request failed for User {user_id}. Status: {response.status_code}, Response: {response.text}")
            return {
                'user_id': str(user_id),
                'name': f'User {user_id}',
                'email': '',
                'department': '',
                'status': 'unknown',
                'api_response': None
            }
            
    except requests.exceptions.RequestException as e:
        print(f"❌ Error fetching employee info for User {user_id}: {e}")
        return {
            'user_id': str(user_id),
            'name': f'User {user_id}',
            'email': '',
            'department': '',
            'status': 'unknown',
            'api_response': None
        }

def validate_registration_images(images):
    """Perform quality checks on the first registration image."""
    if not images:
        return False, "No images provided."

    try:
        # Decode the first image
        image_data = images[0]
        header, encoded = image_data.split(',', 1)
        image_bytes = base64.b64decode(encoded)
        nparr = np.frombuffer(image_bytes, np.uint8)
        img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)

        if img is None:
            return False, "Invalid image format."

        # 1. Face Detection Check
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
        faces = face_cascade.detectMultiScale(gray, 1.1, 4)
        if len(faces) == 0:
            return False, "No face detected. Please ensure your face is clearly visible."

        # 2. Brightness Check
        brightness = np.mean(gray)
        if brightness < 50:
            return False, "Image is too dark. Please move to a brighter area."
        if brightness > 200:
            return False, "Image is too bright. Please avoid direct light."

        return True, "Validation successful."

    except Exception as e:
        print(f"Error during image validation: {e}")
        return False, "An error occurred during image validation."

@app.route('/admin/generate-one-time-code', methods=['POST'])
def generate_one_time_code():
    """Generates a secure, short-lived code for self-registration."""
    data = request.get_json()
    pid = data.get('pid')
    if not pid:
        return jsonify({'error': 'PID is required'}), 400

    # Generate a secure, URL-safe random code
    code = secrets.token_urlsafe(6)
    
    # Store the code with the PID and an expiration time (e.g., 5 minutes)
    one_time_codes[code] = {
        'pid': pid,
        'expires': time.time() + 300  # 5 minutes from now
    }
    
    print(f"Generated code {code} for PID {pid}")
    return jsonify({'success': True, 'code': code})

@app.route('/verify-one-time-code', methods=['POST'])
def verify_one_time_code():
    """Verifies a one-time code and returns the associated user info without deleting the token."""
    data = request.get_json()
    code = data.get('code')
    if not code:
        return jsonify({'error': 'Code is required'}), 400

    # Check if the code exists in the database and is not expired
    token_data = RegistrationToken.find_by_code(code)
    
    if token_data:
        pid = token_data['pid']
        
        # Code is valid, now fetch user info to return to the frontend
        employee_info = fetch_employee_info(pid)
        if not employee_info or employee_info.get('status') == 'unknown':
            return jsonify({'error': f'Could not retrieve information for User ID {pid}.'}), 404

        # Check if the user is already registered
        user_data_file = "user_data.json"
        if os.path.exists(user_data_file):
            with open(user_data_file, 'r') as f:
                user_data = json.load(f)
                if str(pid) in user_data:
                    # Don't delete the token, but inform the user.
                    return jsonify({'error': 'User is already registered.'}), 409

        # Success: Return user info. The token remains valid for the registration step.
        return jsonify({
            'success': True,
            'user_id': employee_info.get('user_id'),
            'name': employee_info.get('name'),
            'email': employee_info.get('email'),
            'department': employee_info.get('department')
        })
    else:
        # If code is invalid or expired, just return the error.
        return jsonify({'error': 'Invalid or expired registration code.'}), 401

@app.route('/verify-user/<pid>', methods=['GET'])
def verify_user(pid):
    """Verify if a user exists and is eligible for self-registration."""
    try:
        # Fetch employee info to confirm the user exists
        employee_info = fetch_employee_info(pid)
        if not employee_info or employee_info.get('status') == 'unknown':
            return jsonify({'error': f'Could not retrieve information for User ID {pid}.'}), 404

        # Check if the user is already registered
        user_data_file = "user_data.json"
        if os.path.exists(user_data_file):
            with open(user_data_file, 'r') as f:
                user_data = json.load(f)
                if pid in user_data:
                    return jsonify({'error': 'User is already registered.'}), 409 # 409 Conflict

        # If user exists and is not registered, return their info
        return jsonify({
            'success': True,
            'user_id': employee_info.get('user_id'),
            'name': employee_info.get('name'),
            'email': employee_info.get('email'),
            'department': employee_info.get('department')
        })

    except Exception as e:
        print(f"Error verifying user {pid}: {e}")
        return jsonify({'error': 'An internal error occurred.'}), 500

@app.route('/register', methods=['POST'])
def register():
    data = request.get_json()
    user_id = str(data.get('userId'))
    images = data.get('images')

    if not user_id or not images:
        return jsonify({'error': 'User ID and images are required'}), 400

    # Server-side validation of the captured images before proceeding
    is_valid, message = validate_registration_images(images)
    if not is_valid:
        return jsonify({'error': message}), 400

    # Fetch employee info to get the name and other details
    employee_info = fetch_employee_info(user_id)
    if not employee_info or employee_info.get('status') == 'unknown':
        return jsonify({'error': f'Could not retrieve information for User ID {user_id}. Please check the ID and try again.'}), 404

    user_name = employee_info.get('name')
    user_email = employee_info.get('email')
    user_department = employee_info.get('department')

    user_folder = os.path.join(FACE_DIR, user_id)
    os.makedirs(user_folder, exist_ok=True)

    for i, image_data in enumerate(images):
        # Decode the base64 image
        header, encoded = image_data.split(',', 1)
        image_bytes = base64.b64decode(encoded)
        
        # Save the image
        with open(os.path.join(user_folder, f'{i}.jpg'), 'wb') as f:
            f.write(image_bytes)

    # Train the model after saving the images
    print("Starting training process...")
    print(f"Training with {len(images)} images for user {user_id}")
    train_face_recognizer()
    print("Training process finished.")
    
    # Verify training worked by checking model file
    if os.path.exists("face_recognizer.yml"):
        print("Model file updated successfully")
    else:
        print("WARNING: Model file not found after training")
    
    # Generate InsightFace embeddings if available
    if INSIGHTFACE_AVAILABLE:
        print("🔄 Generating InsightFace embeddings...")
        insightface_success = generate_insightface_embeddings(user_id, user_folder, user_name, user_department)
        if insightface_success:
            print("✅ InsightFace embedding generation completed")
        else:
            print("⚠️ InsightFace embedding generation failed")
    else:
        print("📝 InsightFace not available, skipping embedding generation")
    
    # Save user data to the database first
    db_success = RegisteredUser.add_or_update(user_id)
    if not db_success:
        return jsonify({'error': 'Failed to save registration metadata to the database.'}), 500

    # Save user data to JSON before deleting photos
    user_data_file = "user_data.json"
    user_data = {}
    if os.path.exists(user_data_file):
        with open(user_data_file, 'r') as f:
            user_data = json.load(f)
    
    # Add/update user data
    user_data[user_id] = {
        'name': user_name,
        'email': user_email,
        'department': user_department,
        'status': 'hired',
        'last_updated': datetime.datetime.now().isoformat()
    }
    
    with open(user_data_file, 'w') as f:
        json.dump(user_data, f, indent=2)
    
    # Keep photos temporarily for better training (delete after 5 minutes for security)
    print("Photos kept temporarily for training verification...")
    print("Photos will be automatically deleted after 5 minutes for security.")
    
    # Schedule photo deletion after 5 minutes
    import threading
    import shutil
    def delete_photos_later():
        import time
        time.sleep(300)  # 5 minutes
        if os.path.exists(user_folder):
            shutil.rmtree(user_folder)
            print(f"Photos for user {user_id} deleted after 5 minutes for security.")
    
    # Start deletion timer in background
    deletion_thread = threading.Thread(target=delete_photos_later)
    deletion_thread.daemon = True
    deletion_thread.start()

    return jsonify({'message': f'User {user_id} ({user_name}) registered successfully with {len(images)} images. Photos deleted for security.'})

@app.route('/recognize', methods=['POST'])
def recognize_face():
    """Main recognition endpoint that routes to the appropriate library based on settings"""
    data = request.get_json()
    image_data = data.get('image')
    
    if not image_data:
        return jsonify({'error': 'Image data is required'}), 400
    
    try:
        # Load settings to determine which library to use
        settings = load_settings()
        library = settings.get('recognition_library', 'insightface')
        
        print(f"🔍 Using recognition library: {library}")
        
        # Route to appropriate recognition method
        if library == 'insightface' and INSIGHTFACE_AVAILABLE:
            # Use InsightFace recognition
            return insightface_recognize_standalone()
        else:
            # Use OpenCV recognition (fallback or explicit choice)
            return opencv_recognize()
            
    except Exception as e:
        print(f"❌ Error in main recognition endpoint: {e}")
        return jsonify({'error': str(e)}), 500

def opencv_recognize():
    """OpenCV-based face recognition"""
    data = request.get_json()
    image_data = data.get('image')
    
    try:
        # Decode the base64 image
        header, encoded = image_data.split(',', 1)
        image_bytes = base64.b64decode(encoded)
        
        # Convert to numpy array
        nparr = np.frombuffer(image_bytes, np.uint8)
        img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
        
        if img is None:
            return jsonify({'error': 'Invalid image data'}), 400
        
        # Convert to grayscale
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        
        # Load face cascade
        face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
        
        # Detect faces with more sensitive parameters for better detection
        faces = face_cascade.detectMultiScale(gray, scaleFactor=1.05, minNeighbors=3, minSize=(20, 20))
        
        print(f"🔍 OpenCV Face detection: Found {len(faces)} faces in image")
        print(f"📐 Image size: {img.shape}")
        
        if len(faces) == 0:
            print("❌ No faces detected - returning 400 error")
            return jsonify({'error': 'No face detected'}), 400
        
        # Get the first detected face
        (x, y, w, h) = faces[0]
        face_img = gray[y:y + h, x:x + w]
        face_img = preprocess_face(face_img)
        
        # Load and use the recognizer
        try:
            recognizer = cv2.face.LBPHFaceRecognizer_create()
            model_file = "face_recognizer.yml"
            
            if not os.path.exists(model_file):
                return jsonify({'error': 'Model file not found. Train the model first.'}), 400
            
            recognizer.read(model_file)
            user_id, confidence = recognizer.predict(face_img)
            
            # Load settings to get OpenCV threshold
            settings = load_settings()
            confidence_threshold = settings.get('opencv_threshold', 150)  # Lower is better in LBPH
            print(f"🔍 Using OpenCV threshold: {confidence_threshold}")
            
            print(f"Recognition attempt - User ID: {user_id}, Confidence: {confidence}, Threshold: {confidence_threshold}")
            print(f"Recognition result: {'SUCCESS' if confidence < confidence_threshold else 'FAILED'}")
            
            if confidence < confidence_threshold:
                # Fetch employee information from live API endpoint
                print(f"✅ RECOGNITION SUCCESS: User {user_id} recognized with confidence {confidence}")
                print(f"🔄 Fetching employee information for User {user_id} from live API...")
                employee_info = fetch_employee_info(user_id)
                print(f"📋 Employee info result: {employee_info}")
                
                # Log attendance with employee information
                import datetime
                import csv
                
                now = datetime.datetime.now()
                timestamp = now.strftime("%Y-%m-%d %H:%M:%S")
                today = now.strftime("%Y-%m-%d")
                
                log_file = "attendance_log.csv"
                
                # Ensure log file exists with headers
                if not os.path.exists(log_file) or os.stat(log_file).st_size == 0:
                    with open(log_file, "w", newline="") as file:
                        writer = csv.writer(file)
                        writer.writerow(["Timestamp", "User ID", "User Name", "Status"])
                
                attendance_status = "Check-in"  # Default
                message = f'User {employee_info["name"]} (ID: {user_id}) checked in successfully'
                
                all_records = []
                today_records = []
                
                with open(log_file, 'r', newline='') as file:
                    reader = csv.DictReader(file)
                    for row in reader:
                        all_records.append(row)
                        if row.get('User ID') == str(user_id) and row.get('Timestamp', '').startswith(today):
                            today_records.append(row)
                
                # Determine attendance status based on today's records
                check_in_exists = any(r.get('Status') == 'Check-in' for r in today_records)
                check_out_exists = any(r.get('Status') == 'Check-out' for r in today_records)
                
                if not check_in_exists:
                    # First recognition today - Check-in
                    attendance_status = "Check-in"
                    message = f'User {employee_info["name"]} (ID: {user_id}) checked in successfully'
                    all_records.append({'Timestamp': timestamp, 'User ID': str(user_id), 'User Name': employee_info["name"], 'Status': attendance_status})
                elif check_in_exists and not check_out_exists:
                    # Second recognition today - Check-out
                    attendance_status = "Check-out"
                    message = f'User {employee_info["name"]} (ID: {user_id}) checked out successfully'
                    all_records.append({'Timestamp': timestamp, 'User ID': str(user_id), 'User Name': employee_info["name"], 'Status': attendance_status})
                elif check_in_exists and check_out_exists:
                    # Third+ recognition today - Update latest checkout
                    attendance_status = "Check-out"
                    message = f'User {employee_info["name"]} (ID: {user_id}) checkout time updated'
                    
                    # Find and update the last checkout record for today
                    for i in reversed(range(len(all_records))):
                        record = all_records[i]
                        if record.get('User ID') == str(user_id) and record.get('Timestamp', '').startswith(today) and record.get('Status') == 'Check-out':
                            record['Timestamp'] = timestamp # Update timestamp
                            record['User Name'] = employee_info["name"] # Also update the name
                            break
                    else: # If no checkout record found (shouldn't happen with logic above, but as fallback)
                        all_records.append({'Timestamp': timestamp, 'User ID': str(user_id), 'User Name': employee_info["name"], 'Status': attendance_status})
                
                # Write all records back to the file
                with open(log_file, "w", newline="") as file:
                    writer = csv.DictWriter(file, fieldnames=["Timestamp", "User ID", "User Name", "Status"])
                    writer.writeheader()
                    writer.writerows(all_records)
                
                response_data = {
                    'success': True,
                    'user_id': int(user_id),
                    'user_name': employee_info["name"],
                    'user_email': employee_info["email"],
                    'user_department': employee_info["department"],
                    'user_status': employee_info["status"],
                    'confidence': round(confidence, 2),
                    'timestamp': timestamp,
                    'attendance_status': attendance_status,
                    'message': message
                }
                print(f"📤 Sending response to frontend: {response_data}")
                return jsonify(response_data)
            else:
                return jsonify({
                    'success': False,
                    'message': 'Face not recognized',
                    'confidence': round(confidence, 2),
                    'debug_info': f'Confidence {confidence} >= threshold {confidence_threshold}'
                })
                
        except Exception as e:
            return jsonify({'error': f'Recognition error: {str(e)}'}), 500
            
    except Exception as e:
                    return jsonify({'error': f'Image processing error: {str(e)}'}), 500

@app.route('/admin/users', methods=['GET'])
@role_required('Admin', 'Super Admin')
def get_all_users(current_user_pid):
    """Get all registered users from the database, enriched with HRIS info via a JOIN."""
    try:
        # This single call now gets all the necessary user data from the database
        users_with_hris = RegisteredUser.get_all_with_hris_info()
        if users_with_hris is None:
            return jsonify({'error': 'Could not fetch users from the database.'}), 500

        enriched_users = []
        for user_row in users_with_hris:
            pid = user_row['pid']
            
            # Check for last attendance (this part still needs to check the CSV)
            attendance_file = "attendance_log.csv"
            last_attendance = None
            if os.path.exists(attendance_file):
                with open(attendance_file, 'r') as f:
                    lines = f.readlines()
                    for line in reversed(lines):
                        if line.strip() and str(pid) in line:
                            last_attendance = line.split(',')[0].strip()
                            break
            
            # The 'data' key is kept for frontend compatibility, containing the joined HRIS info
            enriched_users.append({
                'user_id': pid,
                'name': user_row.get('name', f'User {pid}'),
                'email': user_row.get('email', ''),
                'team_name': user_row.get('team_name', 'N/A'), # Use team_name now
                'last_modified': user_row['last_modified'].isoformat() if user_row['last_modified'] else None,
                'registered_at': user_row['registered_at'].isoformat() if user_row['registered_at'] else None,
                'last_attendance': last_attendance,
                'status': 'Active'
            })
        
        return jsonify({'users': enriched_users})
    except Exception as e:
        print(f"❌ Error in get_all_users (database join version): {e}")
        return jsonify({'error': 'An internal error occurred while fetching database users.'}), 500

@app.route('/admin/users/<user_id>/update-pkl', methods=['POST'])
@role_required('Admin', 'Super Admin')
def update_user_pkl(current_user_pid, user_id):
    """Updates a specific user's PKL file with the latest HRIS data."""
    print(f"🔄 Received request to update PKL for user_id: {user_id}")
    try:
        # 1. Check if the embedding file exists
        embedding_file = os.path.join(EMBEDDINGS_DIR, f"insightface_embeddings_{user_id}.pkl")
        if not os.path.exists(embedding_file):
            print(f"❌ PKL file not found for user {user_id}")
            return jsonify({'error': 'User embedding file not found.'}), 404

        # 2. Fetch the latest employee info from the database
        employee_info = Staff.get_by_pid(user_id)
        if not employee_info:
            print(f"❌ User {user_id} not found in HRIS database.")
            return jsonify({'error': 'User not found in HRIS database.'}), 404

        # 3. Read the existing PKL file
        with open(embedding_file, 'rb') as f:
            data = pickle.load(f)

        # 4. Get new data and check if an update is needed
        new_name = f"{employee_info.get('first_name', '')} {employee_info.get('last_name', '')}".strip()
        new_department = employee_info.get('team_name', 'N/A')
        
        current_name = data.get('name')
        current_department = data.get('department')

        if new_name == current_name and new_department == current_department:
            print(f"✅ No update needed for user {user_id}. Data is already current.")
            return jsonify({'success': True, 'message': 'User data is already up to date.'})

        # 5. Update the data and write it back
        print(f"🔄 Updating user {user_id}: Name '{current_name}' -> '{new_name}', Dept '{current_department}' -> '{new_department}'")
        data['name'] = new_name
        data['department'] = new_department
        
        with open(embedding_file, 'wb') as f:
            pickle.dump(data, f)

        print(f"✅ Successfully updated PKL file for user {user_id}")
        return jsonify({'success': True, 'message': f'Successfully updated PKL file for user {user_id}.'})

    except Exception as e:
        print(f"❌ Fatal error in update_user_pkl for user {user_id}: {e}")
        return jsonify({'error': 'An internal server error occurred.'}), 500




@app.route('/admin/users/<user_id>', methods=['DELETE'])
def delete_user(user_id):
    """Delete a user from the system and retrain model"""
    try:
        # First, delete from the database.
        # We'll proceed with file cleanup even if this fails, to ensure robustness.
        try:
            pid_int = int(user_id)
            RegisteredUser.delete(pid_int)
        except (ValueError, TypeError):
            print(f"Warning: Could not convert user_id '{user_id}' to int for DB deletion. Skipping.")

        # Remove user from JSON data
        user_data_file = "user_data.json"
        user_deleted = False
        
        if os.path.exists(user_data_file):
            with open(user_data_file, 'r') as f:
                user_data = json.load(f)
            
            if user_id in user_data:
                del user_data[user_id]
                user_deleted = True
                
                with open(user_data_file, 'w') as f:
                    json.dump(user_data, f, indent=2)
        
        # Remove any remaining photo folders for this user
        user_folder = os.path.join(FACE_DIR, user_id)
        if os.path.exists(user_folder):
            import shutil
            shutil.rmtree(user_folder)
            user_deleted = True
        
        if user_deleted:
            # Retrain the model to remove the deleted user's data
            print(f"Retraining model after deleting user {user_id}...")
            train_face_recognizer()
            print("Model retrained successfully.")

            # Also delete the InsightFace embedding file
            embedding_file = os.path.join(EMBEDDINGS_DIR, f"insightface_embeddings_{user_id}.pkl")
            if os.path.exists(embedding_file):
                os.remove(embedding_file)
                print(f"Deleted InsightFace embedding file: {embedding_file}")
            
            return jsonify({'message': f'User {user_id} deleted successfully from system and model retrained.'})
        else:
            return jsonify({'error': 'User not found in system'}), 404
    except Exception as e:
        return jsonify({'error': f'Error deleting user: {str(e)}'}), 500

@app.route('/admin/users/<old_user_id>', methods=['PUT'])
def update_user_id(old_user_id):
    """Update user ID (rename folder)"""
    try:
        old_folder = os.path.join(FACE_DIR, old_user_id)
        new_folder = os.path.join(FACE_DIR, new_user_id)
        
        if not os.path.exists(old_folder):
            return jsonify({'error': 'User not found'}), 404
        
        data = request.get_json()
        new_user_id = data.get('new_user_id')
        
        if not new_user_id:
            return jsonify({'error': 'New user ID is required'}), 400
        
        new_folder = os.path.join(FACE_DIR, new_user_id)
        
        if os.path.exists(new_folder):
            return jsonify({'error': 'New user ID already exists'}), 400
        
        os.rename(old_folder, new_folder)
        
        # Note: No retraining needed - photos are deleted for security reasons
        # The trained model retains the user's face data without storing raw images
        
        return jsonify({'message': f'User ID updated from {old_user_id} to {new_user_id}'})
    except Exception as e:
        return jsonify({'error': f'Error updating user ID: {str(e)}'}), 500

@app.route('/admin/users/<user_id>/images', methods=['DELETE'])
def delete_user_images(user_id):
    """Delete all images for a user but keep the folder"""
    try:
        user_folder = os.path.join(FACE_DIR, user_id)
        if not os.path.exists(user_folder):
            return jsonify({'error': 'User not found'}), 404
        
        # Delete all jpg files
        deleted_count = 0
        for filename in os.listdir(user_folder):
            if filename.endswith('.jpg'):
                os.remove(os.path.join(user_folder, filename))
                deleted_count += 1
        
        # Note: No retraining needed - photos are deleted for security reasons
        # The trained model retains the user's face data without storing raw images
        
        return jsonify({'message': f'Deleted {deleted_count} images for user {user_id}'})
    except Exception as e:
        return jsonify({'error': f'Error deleting images: {str(e)}'}), 500

@app.route('/admin/users/<user_id>/images', methods=['GET'])
def get_user_images(user_id):
    """Get all images for a specific user"""
    try:
        user_folder = os.path.join(FACE_DIR, user_id)
        
        # Check if user exists in user_data.json
        user_data_file = "user_data.json"
        user_exists = False
        if os.path.exists(user_data_file):
            with open(user_data_file, 'r') as f:
                user_data = json.load(f)
                user_exists = user_id in user_data
        
        if not user_exists:
            return jsonify({'error': 'User not found'}), 404
        
        # If folder doesn't exist, images were deleted for security
        if not os.path.exists(user_folder):
            return jsonify({
                'images': [],
                'message': 'Images deleted for security after training',
                'status': 'trained'
            })
        
        images = []
        for filename in os.listdir(user_folder):
            if filename.endswith('.jpg'):
                image_path = os.path.join(user_folder, filename)
                # Convert image to base64
                with open(image_path, 'rb') as f:
                    image_data = base64.b64encode(f.read()).decode('utf-8')
                    images.append({
                        'filename': filename,
                        'data': f'data:image/jpeg;base64,{image_data}'
                    })
        
        return jsonify({'images': images})
    except Exception as e:
        return jsonify({'error': f'Error fetching images: {str(e)}'}), 500

@app.route('/admin/settings', methods=['GET'])
def get_admin_settings():
    """Get current admin settings"""
    try:
        settings = load_settings()
        return jsonify({
            'success': True,
            'settings': settings,
            'available_libraries': {
                'opencv': {
                    'name': 'OpenCV LBPH',
                    'accuracy': '70-80%',
                    'description': 'Traditional computer vision approach'
                },
                'insightface': {
                    'name': 'InsightFace',
                    'accuracy': '98%+',
                    'description': 'State-of-the-art deep learning model',
                    'available': INSIGHTFACE_AVAILABLE
                }
            }
        })
    except Exception as e:
        return jsonify({'error': f'Error loading settings: {str(e)}'}), 500

@app.route('/admin/settings', methods=['POST'])
def update_admin_settings():
    """Update admin settings"""
    try:
        data = request.get_json()
        if not data:
            return jsonify({'error': 'No data provided'}), 400
        
        current_settings = load_settings()
        
        # Validate recognition library
        if 'recognition_library' in data:
            library = data['recognition_library']
            if library not in ['opencv', 'insightface']:
                return jsonify({'error': 'Invalid recognition library. Must be "opencv" or "insightface"'}), 400
            
            # Check if InsightFace is available when trying to use it
            if library == 'insightface' and not INSIGHTFACE_AVAILABLE:
                return jsonify({
                    'error': 'InsightFace is not available. Please install InsightFace libraries or use OpenCV.',
                    'available_libraries': ['opencv']
                }), 400
        
        # Validate thresholds
        if 'insightface_threshold' in data:
            threshold = data['insightface_threshold']
            if not isinstance(threshold, (int, float)) or not (0.0 <= threshold <= 1.0):
                return jsonify({'error': 'InsightFace threshold must be between 0.0 and 1.0'}), 400
        
        if 'opencv_threshold' in data:
            threshold = data['opencv_threshold']
            if not isinstance(threshold, (int, float)) or threshold < 0:
                return jsonify({'error': 'OpenCV threshold must be a positive number'}), 400
        
        # Update settings
        current_settings.update(data)
        
        # Save settings
        if save_settings(current_settings):
            return jsonify({
                'success': True,
                'message': 'Settings updated successfully',
                'settings': current_settings
            })
        else:
            return jsonify({'error': 'Failed to save settings'}), 500
            
    except Exception as e:
        return jsonify({'error': f'Error updating settings: {str(e)}'}), 500

@app.route('/attendance/stats', methods=['GET'])
def get_attendance_stats():
    """Get attendance statistics for dashboard"""
    try:
        import datetime
        import csv
        from collections import defaultdict
        
        today = datetime.datetime.now().strftime("%Y-%m-%d")
        week_ago = (datetime.datetime.now() - datetime.timedelta(days=7)).strftime("%Y-%m-%d")
        month_ago = (datetime.datetime.now() - datetime.timedelta(days=30)).strftime("%Y-%m-%d")
        
        # Initialize stats
        stats = {
            'today': 0,
            'thisWeek': 0,
            'thisMonth': 0,
            'recentActivity': []
        }
        
        # Read attendance log if it exists
        log_file = "attendance_log.csv"
        if os.path.exists(log_file):
            with open(log_file, 'r', newline='') as file:
                reader = csv.DictReader(file)
                for row in reader:
                    timestamp = row.get('Timestamp', '')
                    if timestamp:
                        try:
                            # Parse timestamp
                            dt = datetime.datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S")
                            date_str = dt.strftime("%Y-%m-%d")
                            
                            # Count today's check-ins
                            if date_str == today:
                                stats['today'] += 1
                            
                            # Count this week's check-ins
                            if date_str >= week_ago:
                                stats['thisWeek'] += 1
                            
                            # Count this month's check-ins
                            if date_str >= month_ago:
                                stats['thisMonth'] += 1
                            
                            # Add to recent activity (last 10)
                            if len(stats['recentActivity']) < 10:
                                stats['recentActivity'].append({
                                    'id': len(stats['recentActivity']) + 1,
                                    'user': f"User {row.get('User ID', 'Unknown')}",
                                    'name': f"{row.get('User Name', 'Unknown')}",
                                    'time': dt.strftime("%I:%M %p"),
                                    'status': row.get('Status', 'Check-in')
                                })
                        except ValueError:
                            continue
        
        return jsonify(stats)
        
    except Exception as e:
        return jsonify({'error': f'Error getting stats: {str(e)}'}), 500

@app.route('/test/model', methods=['GET'])
def test_model():
    """Test if the face recognition model is working and check alignment"""
    try:
        model_file = "face_recognizer.yml"
        if not os.path.exists(model_file):
            return jsonify({'error': 'Model file not found'}), 400
        
        recognizer = cv2.face.LBPHFaceRecognizer_create()
        recognizer.read(model_file)
        
        # Check registered faces in folders
        face_dir = "registered_faces"
        folder_users = []
        if os.path.exists(face_dir):
            folder_users = [d for d in os.listdir(face_dir) if os.path.isdir(os.path.join(face_dir, d))]
        
        # Check users in JSON data
        json_users = []
        user_data_file = "user_data.json"
        if os.path.exists(user_data_file):
            with open(user_data_file, 'r') as f:
                user_data = json.load(f)
                json_users = list(user_data.keys())
        
        return jsonify({
            'model_exists': True,
            'model_file_size': os.path.getsize(model_file),
            'folder_users': folder_users,
            'json_users': json_users,
            'alignment_issue': len(folder_users) != len(json_users),
            'confidence_threshold': 150,
            'status': 'Model is ready for recognition'
        })
    except Exception as e:
        return jsonify({'error': f'Model test failed: {str(e)}'}), 500

@app.route('/attendance/data', methods=['GET'])
def get_attendance_data():
    """Get detailed attendance data for reports"""
    try:
        import datetime
        import csv
        
        attendance_data = []
        
        # Read attendance log if it exists
        log_file = "attendance_log.csv"
        if os.path.exists(log_file):
            with open(log_file, 'r', newline='') as file:
                reader = csv.DictReader(file)
                for row in reader:
                    timestamp = row.get('Timestamp', '')
                    user_id = row.get('User ID', 'Unknown')
                    name = row.get('User Name', 'Unknown')
                    status = row.get('Status', 'Check-in')
                    
                    if timestamp:
                        try:
                            # Parse timestamp
                            dt = datetime.datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S")
                            
                            # Create attendance record
                            attendance_data.append({
                                'date': dt.strftime("%Y-%m-%d"),
                                'user': f"User {user_id}",
                                'checkin': dt.strftime("%I:%M %p"),
                                'checkout': dt.strftime("%I:%M %p"),  # Same time for now
                                'duration': '8h 0m',  # Default duration
                                'status': status,
                                "name": name
                            })
                        except ValueError:
                            continue
        
        return jsonify({'attendance': attendance_data})
        
    except Exception as e:
        return jsonify({'error': f'Error getting attendance data: {str(e)}'}), 500

@app.route('/attendance/status/<user_id>', methods=['GET'])
def get_attendance_status(user_id):
    """Check if a user has a 'Check-in' record for the current day from the database."""
    try:
        # The model method now handles all the logic
        attendance_info = AttendanceLog.get_status_for_user(user_id)
        return jsonify(attendance_info)

    except Exception as e:
        print(f"Error in get_attendance_status endpoint: {e}")
        return jsonify({'error': 'An internal server error occurred.'}), 500

@app.route('/debug/recognize', methods=['POST'])
def debug_recognition():
    """Debug endpoint to test recognition with detailed output"""
    data = request.get_json()
    image_data = data.get('image')
    
    if not image_data:
        return jsonify({'error': 'Image data is required'}), 400
    
    try:
        # Decode the base64 image
        header, encoded = image_data.split(',', 1)
        image_bytes = base64.b64decode(encoded)
        
        # Convert to numpy array
        nparr = np.frombuffer(image_bytes, np.uint8)
        img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
        
        if img is None:
            return jsonify({'error': 'Invalid image data'}), 400
        
        # Convert to grayscale
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        
        # Load face cascade
        face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
        
        # Detect faces with different parameters
        faces_1 = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=4, minSize=(30, 30))
        faces_2 = face_cascade.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=6, minSize=(50, 50))
        
        debug_info = {
            'image_size': f"{img.shape[1]}x{img.shape[0]}",
            'faces_detected_sensitive': len(faces_1),
            'faces_detected_normal': len(faces_2),
            'face_locations_sensitive': faces_1.tolist() if len(faces_1) > 0 else [],
            'face_locations_normal': faces_2.tolist() if len(faces_2) > 0 else []
        }
        
        if len(faces_1) == 0:
            return jsonify({
                'success': False,
                'message': 'No face detected with sensitive parameters',
                'debug_info': debug_info
            }), 400
        
        # Get the first detected face
        (x, y, w, h) = faces_1[0]
        face_img = gray[y:y + h, x:x + w]
        face_img = preprocess_face(face_img)
        
        # Load and use the recognizer
        try:
            recognizer = cv2.face.LBPHFaceRecognizer_create()
            model_file = "face_recognizer.yml"
            
            if not os.path.exists(model_file):
                return jsonify({'error': 'Model file not found. Train the model first.'}), 400
            
            recognizer.read(model_file)
            user_id, confidence = recognizer.predict(face_img)
            
            # Test different thresholds
            thresholds = [80, 85, 90, 95, 100]
            threshold_results = {}
            
            for threshold in thresholds:
                threshold_results[f'threshold_{threshold}'] = {
                    'recognized': confidence < threshold,
                    'confidence': round(confidence, 2)
                }
            
            debug_info.update({
                'recognition_results': {
                    'user_id': int(user_id),
                    'confidence': round(confidence, 2),
                    'threshold_tests': threshold_results
                }
            })
            
            return jsonify({
                'success': True,
                'user_id': int(user_id),
                'confidence': round(confidence, 2),
                'debug_info': debug_info,
                'message': f'Debug recognition completed'
            })
                
        except Exception as e:
            return jsonify({'error': f'Recognition error: {str(e)}'}), 500
            
    except Exception as e:
        return jsonify({'error': f'Image processing error: {str(e)}'}), 500

# InsightFace API Endpoints
@app.route('/insightface/embeddings', methods=['GET'])
def get_insightface_embeddings():
    """Get all InsightFace embeddings for standalone app"""
    if not INSIGHTFACE_AVAILABLE:
        return jsonify({
            'success': False,
            'error': 'InsightFace not available'
        }), 503
    
    try:
        all_embeddings = []
        all_names = []
        all_ids = []
        
        # Load all embedding files from the dedicated directory
        embedding_files = [f for f in os.listdir(EMBEDDINGS_DIR) if f.startswith('insightface_embeddings_') and f.endswith('.pkl')]
        
        print(f"🔄 Loading {len(embedding_files)} InsightFace embedding files from {EMBEDDINGS_DIR}...")
        
        for embedding_file in embedding_files:
            try:
                file_path = os.path.join(EMBEDDINGS_DIR, embedding_file)
                with open(file_path, 'rb') as f:
                    data = pickle.load(f)
                    user_id = data['user_id']
                    
                    # Get user info from user_data.json
                    if os.path.exists('user_data.json'):
                        with open('user_data.json', 'r') as f:
                            user_data = json.load(f)
                            user_info = user_data.get(user_id, {})
                            user_name = user_info.get('name', f'User {user_id}')
                    else:
                        user_name = f'User {user_id}'
                    
                    # Add embeddings for this user
                    for embedding in data['embeddings']:
                        all_embeddings.append(embedding.tolist())
                        all_names.append(user_name)
                        all_ids.append(user_id)
                        
                print(f"✅ Loaded embeddings for {user_name} (ID: {user_id})")
                        
            except Exception as e:
                print(f"⚠️ Error loading {embedding_file}: {e}")
                continue
        
        print(f"✅ Total embeddings loaded: {len(all_embeddings)}")
        
        return jsonify({
            'success': True,
            'embeddings': all_embeddings,
            'names': all_names,
            'ids': all_ids,
            'count': len(all_embeddings)
        })
        
    except Exception as e:
        print(f"❌ Error in get_insightface_embeddings: {e}")
        return jsonify({
            'success': False,
            'error': str(e)
        }), 500

def check_sharpness(image):
    """Calculates the sharpness of an image."""
    try:
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        return cv2.Laplacian(gray, cv2.CV_64F).var()
    except Exception:
        return 0

def check_skin_texture(image):
    """Calculates the Cr channel variance, an indicator of skin texture."""
    try:
        ycbcr = cv2.cvtColor(image, cv2.COLOR_BGR2YCrCb)
        return ycbcr[:, :, 1].var()
    except Exception:
        return 0

def check_micro_motion(images):
    """Calculates the average motion between frames."""
    try:
        if len(images) < 2: return 0
        prev_gray = cv2.cvtColor(images[0], cv2.COLOR_BGR2GRAY)
        total_flow = 0
        for i in range(1, len(images)):
            current_gray = cv2.cvtColor(images[i], cv2.COLOR_BGR2GRAY)
            flow = cv2.calcOpticalFlowFarneback(prev_gray, current_gray, None, 0.5, 3, 15, 3, 5, 1.2, 0)
            magnitude, _ = cv2.cartToPolar(flow[..., 0], flow[..., 1])
            total_flow += np.mean(magnitude)
            prev_gray = current_gray
        return total_flow / (len(images) - 1)
    except Exception:
        return 0

@app.route('/insightface/recognize', methods=['POST'])
def insightface_recognize():
    """Recognize multiple faces using InsightFace with individual liveness checks."""
    start_time = time.time()
    try:
        data = request.get_json()
        image_data_list = data.get('images') 
        
        if not image_data_list or len(image_data_list) < 3:
            return jsonify({'error': 'A list of 3 images is required'}), 400
        
        # --- Image Decoding ---
        t0 = time.time()
        decoded_images = []
        for image_data in image_data_list:
            header, encoded = image_data.split(',', 1)
            image_bytes = base64.b64decode(encoded)
            nparr = np.frombuffer(image_bytes, np.uint8)
            img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
            if img is None:
                return jsonify({'success': False, 'error': 'Invalid image data in list'}), 400
            decoded_images.append(img)
        t1 = time.time()
        print(f"Image Decoding: {(t1 - t0) * 1000:.2f} ms")

        middle_frame = decoded_images[1]
        
        if not initialize_insightface():
            return jsonify({'success': False, 'error': 'Failed to initialize InsightFace model'}), 500
        
        # --- Face Detection & Embedding ---
        t2 = time.time()
        faces = insightface_model.get(middle_frame)
        t3 = time.time()
        print(f"Face Detection & Embedding ({len(faces)} faces): {(t3 - t2) * 1000:.2f} ms")
        
        if not faces:
            return jsonify({'success': False, 'users': [], 'error': 'No faces detected'}), 400
        
        # --- Liveness, Matching & DB Logging ---
        t4 = time.time()
        recognized_users = []
        matched_users = []
        
        # --- Pre-load all known embeddings for efficiency ---
        all_known_embeddings = []
        embedding_files = [f for f in os.listdir(EMBEDDINGS_DIR) if f.startswith('insightface_embeddings_') and f.endswith('.pkl')]
        for embedding_file in embedding_files:
            try:
                file_path = os.path.join(EMBEDDINGS_DIR, embedding_file)
                with open(file_path, 'rb') as f:
                    data = pickle.load(f)
                    for known_embedding in data['embeddings']:
                        all_known_embeddings.append({
                            'user_id': data['user_id'],
                            'name': data.get('name', f'User {data["user_id"]}'),
                            'department': data.get('department', 'N/A'),
                            'embedding': known_embedding
                        })
            except Exception as e:
                print(f" Error loading {embedding_file}: {e}")
                continue

        # --- Process each detected face ---
        for face in faces:
            bbox = face.bbox.astype(int)
            
            # Crop the face for liveness checks
            padding = 20
            x1, y1, x2, y2 = max(0, bbox[0] - padding), max(0, bbox[1] - padding), min(middle_frame.shape[1], bbox[2] + padding), min(middle_frame.shape[0], bbox[3] + padding)
            face_crop = middle_frame[y1:y2, x1:x2]

            # --- LIVENESS DETECTION for each face ---
            sharpness_score = check_sharpness(face_crop)
            texture_score = check_skin_texture(face_crop)
            motion_score = check_micro_motion(decoded_images)
            face_area = (bbox[2] - bbox[0]) * (bbox[3] - bbox[1])

            # --- Get thresholds from environment variables ---
            MIN_SHARPNESS_THRESHOLD = float(os.environ.get("MIN_SHARPNESS_THRESHOLD", 20))
            MAX_SHARPNESS_THRESHOLD = float(os.environ.get("MAX_SHARPNESS_THRESHOLD", 3000))
            MIN_TEXTURE_THRESHOLD = float(os.environ.get("MIN_TEXTURE_THRESHOLD", 8))
            MAX_TEXTURE_THRESHOLD = float(os.environ.get("MAX_TEXTURE_THRESHOLD", 500))
            MIN_MOTION_THRESHOLD = float(os.environ.get("MIN_MOTION_THRESHOLD", 0.08))
            MAX_MOTION_THRESHOLD = float(os.environ.get("MAX_MOTION_THRESHOLD", 0.8))
            MIN_FACE_AREA = float(os.environ.get("MIN_FACE_AREA", 8000))
            MAX_FACE_AREA = float(os.environ.get("MAX_FACE_AREA", 120000))

            # Detailed logging for debugging
            print(f"--- Liveness Check for face at {bbox} ---")
            print(f"  - Sharpness: {sharpness_score:.2f} (Threshold: {MIN_SHARPNESS_THRESHOLD} - {MAX_SHARPNESS_THRESHOLD})")
            print(f"  - Texture:   {texture_score:.2f} (Threshold: {MIN_TEXTURE_THRESHOLD} - {MAX_TEXTURE_THRESHOLD})")
            print(f"  - Motion:    {motion_score:.4f} (Threshold: {MIN_MOTION_THRESHOLD} - {MAX_MOTION_THRESHOLD})")
            print(f"  - Face Area: {face_area} (Threshold: {MIN_FACE_AREA} - {MAX_FACE_AREA})")
            
            # --- Anti-Spoofing Heuristics ---
            is_live = True
            rejection_reason = ""

            # Rule 1: Basic Sanity Checks
            if not (MIN_SHARPNESS_THRESHOLD < sharpness_score < MAX_SHARPNESS_THRESHOLD):
                is_live = False
                rejection_reason = "Sharpness out of range"
            elif not (MIN_TEXTURE_THRESHOLD < texture_score < MAX_TEXTURE_THRESHOLD):
                is_live = False
                rejection_reason = "Texture out of range"
            elif not (MIN_MOTION_THRESHOLD < motion_score < MAX_MOTION_THRESHOLD):
                is_live = False
                rejection_reason = "Motion out of range"
            elif not (MIN_FACE_AREA < face_area < MAX_FACE_AREA):
                is_live = False
                rejection_reason = "Face Area out of range"

            # Rule 2: The "Phone Screen" Heuristic
            # This detects the signature of a phone screen: low sharpness but unnaturally high texture from the pixel grid.
            if sharpness_score < 200 and texture_score > 60:
                is_live = False
                rejection_reason = "Spoof Detected: Phone screen pattern (low sharpness, high texture)"

            if not is_live:
                print(f"  - ❌ RESULT: Liveness check FAILED. Reason: {rejection_reason}")
                continue # Skip to the next face
            
            print("  - ✅ RESULT: Liveness check PASSED.")

            # --- RECOGNITION for each face ---
            face_embedding = face.embedding
            best_match_id = None
            best_similarity = 0.0
            settings = load_settings()
            threshold = float(settings.get('insightface_threshold', 0.6))

            for known in all_known_embeddings:
                # Calculate cosine similarity correctly
                similarity = float(np.dot(face_embedding, known['embedding']) / (np.linalg.norm(face_embedding) * np.linalg.norm(known['embedding'])))
                if similarity > best_similarity and similarity > threshold:
                    best_similarity = similarity
                    best_match_id = known['user_id']
                    best_match_name = known['name']
                    best_match_department = known['department']

            if best_match_id:
                matched_users.append({
                    'user_id': best_match_id,
                    'employee_info': {'name': best_match_name, 'department': best_match_department},
                    'similarity': best_similarity
                })

        # --- Post-processing: Log attendance only for successfully matched users ---
        if matched_users:
            import csv
            import datetime
            now = datetime.datetime.now()
            timestamp = now.strftime("%Y-%m-%d %H:%M:%S")
            today = now.strftime("%Y-%m-%d")
            log_file = "attendance_log.csv"
            
            fieldnames = ["Timestamp", "User ID", "User Name", "Status"]
            
            all_records = []
            if os.path.exists(log_file) and os.stat(log_file).st_size > 0:
                with open(log_file, 'r', newline='') as f:
                    reader = csv.DictReader(f)
                    all_records = list(reader)

            with open(log_file, "a", newline="") as f:
                writer = csv.DictWriter(f, fieldnames=fieldnames)
                
                if f.tell() == 0:
                    writer.writeheader()

                for user in matched_users:
                    best_match_id = user['user_id']
                    employee_info = user['employee_info']
                    
                    has_checked_in_today = any(
                        r.get('User ID') == str(best_match_id) and 
                        r.get('Timestamp', '').startswith(today) and 
                        r.get('Status') == 'Check-in' 
                        for r in all_records
                    )
                    
                    attendance_status = "Check-out" if has_checked_in_today else "Check-in"
                    message = f'User {employee_info["name"]} {attendance_status.lower()} successfully'

                    print(f"✅ SUCCESSFUL MATCH: User ID: {best_match_id}, Name: {employee_info['name']}, Similarity: {user['similarity']:.4f}")

                    new_record = {
                        'Timestamp': timestamp, 
                        'User ID': str(best_match_id), 
                        'User Name': employee_info["name"], 
                        'Status': attendance_status
                    }
                    writer.writerow(new_record)
                    all_records.append(new_record)

                    recognized_users.append({
                        'user_id': best_match_id,
                        'user_name': employee_info["name"],
                        'user_department': employee_info["department"],
                        'confidence': round(float(user['similarity']), 3),
                        'message': message,
                        'attendance_status': attendance_status,
                        'timestamp': timestamp
                    })
        
        t5 = time.time()
        print(f"Liveness, Matching & DB Logging: {(t5 - t4) * 1000:.2f} ms")
        
        end_time = time.time()
        print(f"Total Request Time: {(end_time - start_time) * 1000:.2f} ms")

        if recognized_users:
            return jsonify({
                'success': True,
                'users': recognized_users
            })
        else:
            return jsonify({'success': False, 'users': [], 'error': 'No known faces found'}), 400
            
    except Exception as e:
        print(f" FATAL ERROR in /insightface/recognize: {e}")
        return jsonify({'success': False, 'error': 'An unexpected server error occurred.'}), 500

def insightface_recognize_standalone():
    """Recognize face using InsightFace"""
    if not INSIGHTFACE_AVAILABLE:
        return jsonify({
            'success': False,
            'error': 'InsightFace not available'
        }), 503
    
    try:
        data = request.get_json()
        image_data = data.get('image')
        
        if not image_data:
            return jsonify({'error': 'No image data provided'}), 400
        
        # Decode image
        header, encoded = image_data.split(',', 1)
        image_bytes = base64.b64decode(encoded)
        nparr = np.frombuffer(image_bytes, np.uint8)
        img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
        
        if img is None:
            return jsonify({
                'success': False,
                'error': 'Invalid image data'
            }), 400
        
        # Initialize InsightFace if not already done
        if not initialize_insightface():
            return jsonify({
                'success': False,
                'error': 'Failed to initialize InsightFace model'
            }), 500
        
        # Get face embedding with more sensitive detection
        print(f"🔍 Processing image: {img.shape}")
        faces = insightface_model.get(img, max_num=1)
        print(f"🔍 Faces detected: {len(faces) if faces else 0}")
        
        if not faces:
            # Try OpenCV face detection as fallback
            print("🔄 InsightFace failed, trying OpenCV face detection...")
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
            opencv_faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=3, minSize=(30, 30))
            print(f"🔍 OpenCV faces detected: {len(opencv_faces)}")
            
            if len(opencv_faces) == 0:
                return jsonify({
                    'success': False,
                    'error': 'No face detected',
                    'message': 'Please ensure your face is clearly visible in the camera',
                    'fallback': 'Try adjusting lighting or camera angle',
                    'debug_info': {
                        'image_shape': img.shape,
                        'image_channels': img.shape[2] if len(img.shape) > 2 else 1,
                        'insightface_detection': 'failed',
                        'opencv_detection': 'failed'
                    }
                }), 400
            else:
                return jsonify({
                    'success': False,
                    'error': 'Face detected but InsightFace processing failed',
                    'message': 'Face found but unable to process with InsightFace',
                    'fallback': 'Try using OpenCV recognition instead',
                    'debug_info': {
                        'image_shape': img.shape,
                        'opencv_faces_found': len(opencv_faces),
                        'insightface_detection': 'failed',
                        'opencv_detection': 'success'
                    }
                }), 400
        
        face_embedding = faces[0].embedding
        
        # Compare with known embeddings
        best_match = None
        best_similarity = 0.0
        
        # Load settings to get threshold
        settings = load_settings()
        threshold = float(settings.get('insightface_threshold', 0.6))  # Cosine similarity threshold (higher is better)
        print(f"🔍 Using InsightFace threshold: {threshold}")
        
        # Load all embeddings from the correct directory
        embedding_files = [f for f in os.listdir(EMBEDDINGS_DIR) if f.startswith('insightface_embeddings_') and f.endswith('.pkl')]
        
        for embedding_file in embedding_files:
            try:
                # Construct the full path to the file
                file_path = os.path.join(EMBEDDINGS_DIR, embedding_file)
                with open(file_path, 'rb') as f:
                    data = pickle.load(f)
                    user_id = data['user_id']
                    
                    for known_embedding in data['embeddings']:
                        # Calculate cosine similarity (higher is better)
                        similarity = float(np.dot(face_embedding, known_embedding) / (np.linalg.norm(face_embedding) * np.linalg.norm(known_embedding)))
                        print(f"🔍 User {user_id} similarity: {similarity:.3f} (threshold: {threshold})")
                        
                        if similarity > best_similarity and similarity > threshold:
                            best_similarity = float(similarity)  # Convert numpy.float32 to Python float
                            best_match = user_id
                            print(f"✅ New best match: User {user_id} with similarity {similarity:.3f}")
                            
            except Exception as e:
                print(f"⚠️ Error loading {embedding_file}: {e}")
                continue
        
        if best_match:
            confidence = float(best_similarity)  # Convert numpy.float32 to Python float
            
            # Fetch employee information from live API endpoint
            print(f"✅ RECOGNITION SUCCESS: User {best_match} recognized with similarity {confidence}")
            print(f"🔄 Fetching employee information for User {best_match} from live API...")
            employee_info = fetch_employee_info(best_match)
            print(f"📋 Employee info result: {employee_info}")

            # --- ATTENDANCE LOGIC ---
            now = datetime.datetime.now()
            timestamp = now.strftime("%Y-%m-%d %H:%M:%S")
            today = now.strftime("%Y-%m-%d")
            log_file = "attendance_log.csv"
            
            # Ensure log file exists with headers
            if not os.path.exists(log_file) or os.stat(log_file).st_size == 0:
                with open(log_file, "w", newline="") as file:
                    writer = csv.writer(file)
                    writer.writerow(["Timestamp", "User ID", "User Name", "Status"])
            
            all_records = []
            today_records = []
            
            with open(log_file, 'r', newline='') as file:
                reader = csv.DictReader(file)
                for row in reader:
                    all_records.append(row)
                    if row.get('User ID') == str(best_match) and row.get('Timestamp', '').startswith(today):
                        today_records.append(row)
            
            # Determine attendance status
            check_in_exists = any(r.get('Status') == 'Check-in' for r in today_records)
            
            if not check_in_exists:
                attendance_status = "Check-in"
                message = f'User {employee_info["name"]} checked in successfully'
            else:
                attendance_status = "Check-out"
                message = f'User {employee_info["name"]} checked out successfully'

            # Append the new record to the log
            with open(log_file, 'a', newline='') as file:
                writer = csv.writer(file)
                writer.writerow([timestamp, str(best_match), employee_info["name"], attendance_status])

            return jsonify({
                'success': True,
                'user_id': best_match,
                'user_name': employee_info["name"],
                'user_email': employee_info["email"],
                'user_department': employee_info["department"],
                'user_status': employee_info["status"],
                'confidence': round(confidence, 3),
                'timestamp': timestamp,
                'attendance_status': attendance_status,
                'message': message,
                'method': 'insightface'
            })
        else:
            # Enhanced error response with fallback information
            return jsonify({
                'success': False,
                'error': 'No match found',
                'threshold': float(threshold),
                'message': 'Face detected but no matching user found in database',
                'fallback': 'Try registering the user or check if embeddings exist'
            }), 400
            
    except Exception as e:
        print(f"❌ Error in insightface_recognize: {e}")
        # Fallback to OpenCV recognition if InsightFace fails
        try:
            print("🔄 Attempting fallback to OpenCV recognition...")
            # Call the regular OpenCV recognition endpoint
            from flask import current_app
            with current_app.test_request_context('/recognize', method='POST', json={'image': data.get('image')}):
                opencv_result = recognize_face()
                if hasattr(opencv_result, 'get_json'):
                    opencv_data = opencv_result.get_json()
                    if opencv_data.get('success'):
                        opencv_data['method'] = 'opencv_fallback'
                        opencv_data['fallback_reason'] = f'InsightFace failed: {str(e)}'
                        return jsonify(opencv_data)
        except Exception as fallback_error:
            print(f"❌ Fallback to OpenCV also failed: {fallback_error}")
        
        return jsonify({
            'success': False,
            'error': str(e),
            'fallback_attempted': True,
            'message': 'Both InsightFace and OpenCV recognition failed'
        }), 500

@app.route('/admin/sync-database', methods=['POST'])
@role_required('Admin', 'Super Admin')
def sync_database(current_user_pid):
    """
    Synchronizes the user_data.json file with the registered_users database table.
    This is a manual utility to ensure consistency.
    """
    try:
        user_data_file = "user_data.json"
        if not os.path.exists(user_data_file):
            return jsonify({'error': 'user_data.json not found.'}), 404

        with open(user_data_file, 'r') as f:
            user_data = json.load(f)

        synced_count = 0
        failed_count = 0
        
        for pid_str in user_data.keys():
            try:
                pid = int(pid_str)
                # The add_or_update method handles both inserts and updates.
                success = RegisteredUser.add_or_update(pid)
                if success:
                    synced_count += 1
                else:
                    failed_count += 1
            except (ValueError, TypeError):
                print(f"Skipping invalid PID from user_data.json: {pid_str}")
                failed_count += 1
                continue
        
        message = f"Database synchronization complete. Synced: {synced_count}, Failed: {failed_count}."
        print(f"✅ {message}")
        return jsonify({'success': True, 'message': message, 'synced': synced_count, 'failed': failed_count})

    except Exception as e:
        print(f"❌ Error during database synchronization: {e}")
        return jsonify({'error': 'An internal error occurred during synchronization.'}), 500

# Ensure the tables exist on startup
RegistrationToken.create_table_if_not_exists()
Role.create_tables_if_not_exists()
RegisteredUser.create_table_if_not_exists()
from franai.models.attendance_model import AttendanceLog
AttendanceLog.create_table_if_not_exists()

if __name__ == '__main__':
    from franai.services.scheduler import start_scheduler
    start_scheduler()
    app.run(debug=True, port=5001)