#!/usr/bin/env python3
"""
Enhanced Standalone Face Recognition Application
Features: Real-time detection, recognition, history, statistics, settings, and attendance system
"""

import cv2
import numpy as np
import customtkinter as ctk
from tkinter import ttk, messagebox
import os
import json
import csv
from datetime import datetime, timedelta
import threading
from PIL import Image, ImageTk
import calendar
from pathlib import Path
import time
import requests
import base64
import insightface
import onnxruntime
import pickle

# Set appearance mode
ctk.set_appearance_mode("dark")

class FaceRecognitionApp:
    def __init__(self):
        self.root = ctk.CTk()
        self.root.title("Enhanced Face Recognition System v4.0 - InsightFace")
        self.root.geometry("1400x900")
        
        # Initialize variables
        self.is_running = False
        self.cap = None
        self.user_data = {}
        self.current_camera_index = 0
        
        # InsightFace model
        self.face_app = None
        self.known_face_embeddings = []
        self.known_face_names = [] # Tuple of (name, user_id)
        
        # Settings variables
        self.confidence_threshold = 0.5  # Cosine similarity, higher is better
        
        # Backend endpoint configuration
        self.backend_url = "http://localhost:5001"
        
        # Attendance tracking
        self.attendance_log = []
        
        # Load models and data
        self.load_models()
        self.load_attendance_data()
        
        # Create UI
        self.create_ui()
        
        # Start camera
        self.start_camera()
        
    def load_models(self):
        """Load InsightFace model and face embeddings"""
        try:
            self.face_app = insightface.app.FaceAnalysis(providers=['CPUExecutionProvider'])
            self.face_app.prepare(ctx_id=0, det_size=(640, 640))
            print("✅ InsightFace model loaded.")

            # Load user data
            if os.path.exists("user_data.json"):
                with open("user_data.json", "r") as f:
                    self.user_data = json.load(f)
                print(f"✅ Loaded {len(self.user_data)} users")
            else:
                print("⚠️ No user_data.json found")
                messagebox.showwarning("Warning", "user_data.json not found. Recognition will not work.")

            # Load face embeddings
            self.load_face_embeddings()

        except Exception as e:
            messagebox.showerror("Error", f"Failed to load models: {e}")
            self.root.quit()

    def load_face_embeddings(self):
        """Load face embeddings from web backend"""
        try:
            # Try to load from web backend first
            response = requests.get(f"{self.backend_url}/insightface/embeddings", timeout=10)
            
            if response.status_code == 200:
                data = response.json()
                if data.get('success') and data.get('embeddings'):
                    self.known_face_embeddings = [np.array(emb) for emb in data['embeddings']]
                    # Convert to tuples of (name, user_id) for compatibility
                    self.known_face_names = [(name, user_id) for name, user_id in zip(data['names'], data['ids'])]
                    self.known_face_ids = data['ids']
                    print(f"✅ Loaded {len(self.known_face_embeddings)} face embeddings from web backend.")
                    print(f"📊 Embeddings for users: {set(data['ids'])}")
                    return
                else:
                    print("⚠️ No embeddings available from web backend")
            else:
                print(f"⚠️ Failed to load embeddings from web backend: {response.status_code}")
                
        except Exception as e:
            print(f"⚠️ Error loading embeddings from web backend: {e}")
        
        # Fallback: try local file
        embeddings_file = "face_embeddings_insightface.pkl"
        if os.path.exists(embeddings_file):
            try:
                with open(embeddings_file, 'rb') as f:
                    data = pickle.load(f)
                    self.known_face_embeddings = data['embeddings']
                    # Convert to tuples of (name, user_id) for compatibility
                    if 'names' in data and 'ids' in data:
                        self.known_face_names = [(name, user_id) for name, user_id in zip(data['names'], data['ids'])]
                    else:
                        # Fallback for old format
                        self.known_face_names = [(name, user_id) for name, user_id in data['names']]
                print(f"✅ Loaded {len(self.known_face_embeddings)} face embeddings from local cache.")
                return
            except Exception as e:
                print(f"❌ Error loading local embeddings: {e}")
        
        # No embeddings available
        self.known_face_embeddings = []
        self.known_face_names = []
        self.known_face_ids = []
        print("⚠️ No face embeddings available. Please register users through the web interface first.")
            
    def load_attendance_data(self):
        """Load attendance data from local CSV file"""
        try:
            if os.path.exists("attendance_log.csv"):
                with open("attendance_log.csv", "r") as f:
                    reader = csv.DictReader(f)
                    self.attendance_log = list(reader)
                print(f"✅ Loaded {len(self.attendance_log)} attendance records from local file")
            else:
                self.attendance_log = []
        except Exception as e:
            print(f"Error loading attendance data: {e}")
            self.attendance_log = []
            
    def create_ui(self):
        """Create the user interface"""
        # Main frame
        main_frame = ctk.CTkFrame(self.root)
        main_frame.pack(fill="both", expand=True, padx=10, pady=10)
        
        # Title
        title_label = ctk.CTkLabel(main_frame, text="Enhanced Face Recognition System (InsightFace)", 
                                 font=ctk.CTkFont(size=24, weight="bold"))
        title_label.pack(pady=(0, 20))
        
        # Content frame with two columns
        content_frame = ctk.CTkFrame(main_frame)
        content_frame.pack(fill="both", expand=True)
        
        # Left column - Camera and controls
        left_frame = ctk.CTkFrame(content_frame)
        left_frame.pack(side="left", fill="both", expand=True, padx=(0, 10))
        
        # Camera frame with fixed size
        camera_frame = ctk.CTkFrame(left_frame)
        camera_frame.pack(pady=(0, 20))
        
        # Camera label (fixed size)
        self.camera_label = ctk.CTkLabel(camera_frame, text="Camera Loading...", 
                                       width=480, height=360)
        self.camera_label.pack(padx=10, pady=10)
        
        # Control buttons
        controls_frame = ctk.CTkFrame(left_frame)
        controls_frame.pack(fill="x", pady=(0, 20))
        
        self.start_button = ctk.CTkButton(controls_frame, text="Start Recognition", 
                                        command=self.start_recognition)
        self.start_button.pack(side="left", padx=(10, 5), pady=10)
        
        self.stop_button = ctk.CTkButton(controls_frame, text="Stop Recognition", 
                                       command=self.stop_recognition, state="disabled")
        self.stop_button.pack(side="left", padx=5, pady=10)
        
        # Settings button
        settings_button = ctk.CTkButton(controls_frame, text="Settings", 
                                     command=self.open_settings)
        settings_button.pack(side="right", padx=(5, 10), pady=10)
        
        # Reports button
        reports_button = ctk.CTkButton(controls_frame, text="Reports", 
                                     command=self.open_reports)
        reports_button.pack(side="right", padx=(5, 10), pady=10)
        
        # Status label
        self.status_label = ctk.CTkLabel(left_frame, text="Status: Ready", 
                                       font=ctk.CTkFont(size=14))
        self.status_label.pack(pady=(0, 10))
        
        # Right column - Recognition history
        right_frame = ctk.CTkFrame(content_frame)
        right_frame.pack(side="right", fill="both", expand=True)
        
        # History title
        history_title = ctk.CTkLabel(right_frame, text="Recognition History", 
                                   font=ctk.CTkFont(size=18, weight="bold"))
        history_title.pack(pady=(0, 10))
        
        # History treeview
        columns = ("Time", "User", "Team", "Confidence", "Attendance")
        self.history_tree = ttk.Treeview(right_frame, columns=columns, show="headings", height=15)
        
        # Configure columns
        self.history_tree.heading("Time", text="Time")
        self.history_tree.heading("User", text="User")
        self.history_tree.heading("Team", text="Team")
        self.history_tree.heading("Confidence", text="Confidence")
        self.history_tree.heading("Attendance", text="Attendance")
        
        # Set column widths
        self.history_tree.column("Time", width=100)
        self.history_tree.column("User", width=120)
        self.history_tree.column("Team", width=100)
        self.history_tree.column("Confidence", width=80)
        self.history_tree.column("Attendance", width=100)
        
        # Add scrollbar
        history_scrollbar = ttk.Scrollbar(right_frame, orient="vertical", command=self.history_tree.yview)
        self.history_tree.configure(yscrollcommand=history_scrollbar.set)
        
        self.history_tree.pack(side="left", fill="both", expand=True, padx=(10, 0))
        history_scrollbar.pack(side="right", fill="y", padx=(0, 10))
        
        # Clear history button
        clear_button = ctk.CTkButton(right_frame, text="Clear History", 
                                   command=self.clear_history)
        clear_button.pack(pady=10)
        
    def open_settings(self):
        """Open settings dialog with threshold and sensitivity sliders"""
        settings_window = ctk.CTkToplevel(self.root)
        settings_window.title("Recognition Settings")
        settings_window.geometry("500x400")
        settings_window.transient(self.root)
        settings_window.grab_set()
        
        # Settings content
        title = ctk.CTkLabel(settings_window, text="Recognition Settings", 
                           font=ctk.CTkFont(size=18, weight="bold"))
        title.pack(pady=20)
        
        # Camera selection frame
        camera_frame = ctk.CTkFrame(settings_window)
        camera_frame.pack(fill="x", padx=20, pady=10)
        
        ctk.CTkLabel(camera_frame, text="Camera Selection:", 
                    font=ctk.CTkFont(size=14, weight="bold")).pack(pady=5)
        
        available_cameras = self.get_available_cameras()
        if available_cameras:
            camera_var = ctk.StringVar(value=self.get_camera_display_name(self.current_camera_index, 0, 0))
            camera_dropdown = ctk.CTkOptionMenu(camera_frame, 
                                              values=available_cameras,
                                              variable=camera_var,
                                              command=self.on_camera_change)
            camera_dropdown.pack(pady=5)
        else:
            ctk.CTkLabel(camera_frame, text="No cameras detected").pack(pady=5)
        
        # Confidence threshold
        confidence_frame = ctk.CTkFrame(settings_window)
        confidence_frame.pack(fill="x", padx=20, pady=10)
        
        ctk.CTkLabel(confidence_frame, text="Confidence Threshold (Cosine Similarity):", 
                    font=ctk.CTkFont(size=14, weight="bold")).pack(pady=5)
        ctk.CTkLabel(confidence_frame, text="Higher is stricter. Recommended: 0.5",
                    font=ctk.CTkFont(size=10)).pack(pady=(0,5))
        self.confidence_slider = ctk.CTkSlider(confidence_frame, from_=0.4, to=0.8, 
                                            number_of_steps=40, command=self.on_threshold_change)
        self.confidence_slider.set(self.confidence_threshold)
        self.confidence_slider.pack(pady=5)
        
        self.threshold_label = ctk.CTkLabel(confidence_frame, text=f"Current: {self.confidence_threshold:.2f}")
        self.threshold_label.pack(pady=(0, 5))
        
        # Close button
        close_button = ctk.CTkButton(settings_window, text="Close", 
                                   command=settings_window.destroy)
        close_button.pack(pady=20)
        
    def open_reports(self):
        """Open reports dialog with attendance logs"""
        reports_window = ctk.CTkToplevel(self.root)
        reports_window.title("Attendance Reports")
        reports_window.geometry("1000x700")
        reports_window.transient(self.root)
        reports_window.grab_set()
        
        # Title and description
        title_label = ctk.CTkLabel(reports_window, text="Enhanced Attendance Log", 
                                 font=ctk.CTkFont(size=20, weight="bold"))
        title_label.pack(pady=(0, 5))
        
        desc_label = ctk.CTkLabel(reports_window, text="View and manage attendance records with advanced filtering", 
                                 font=ctk.CTkFont(size=12))
        desc_label.pack(pady=(0, 20))
        
        # Filters frame
        filters_frame = ctk.CTkFrame(reports_window)
        filters_frame.pack(fill="x", padx=20, pady=(0, 20))
        
        # Date range filters
        date_frame = ctk.CTkFrame(filters_frame)
        date_frame.pack(fill="x", padx=10, pady=10)
        
        ctk.CTkLabel(date_frame, text="Date Range Filter:", 
                    font=ctk.CTkFont(size=14, weight="bold")).pack(anchor="w", pady=(0, 5))
        
        date_controls_frame = ctk.CTkFrame(date_frame)
        date_controls_frame.pack(fill="x")
        
        # Start date
        ctk.CTkLabel(date_controls_frame, text="From:").pack(side="left", padx=(10, 5))
        self.start_date_var = ctk.StringVar(value=self.get_first_day_of_month())
        start_date_entry = ctk.CTkEntry(date_controls_frame, textvariable=self.start_date_var, width=120)
        start_date_entry.pack(side="left", padx=(0, 10))
        
        # End date
        ctk.CTkLabel(date_controls_frame, text="To:").pack(side="left", padx=(10, 5))
        self.end_date_var = ctk.StringVar(value=datetime.now().strftime("%Y-%m-%d"))
        end_date_entry = ctk.CTkEntry(date_controls_frame, textvariable=self.end_date_var, width=120)
        end_date_entry.pack(side="left", padx=(0, 10))
        
        # This month button
        this_month_btn = ctk.CTkButton(date_controls_frame, text="This Month", 
                                      command=self.set_this_month_range, width=100)
        this_month_btn.pack(side="left", padx=(10, 0))
        
        # Search frame
        search_frame = ctk.CTkFrame(filters_frame)
        search_frame.pack(fill="x", padx=10, pady=(0, 10))
        
        ctk.CTkLabel(search_frame, text="Search by User ID or Name:").pack(side="left", padx=(10, 5))
        self.search_var = ctk.StringVar()
        search_entry = ctk.CTkEntry(search_frame, textvariable=self.search_var, width=200)
        search_entry.pack(side="left", padx=(0, 10))
        search_entry.bind('<KeyRelease>', self.filter_attendance_data)
        
        # Statistics frame
        stats_frame = ctk.CTkFrame(reports_window)
        stats_frame.pack(fill="x", padx=20, pady=(0, 20))
        
        # Create stats grid
        stats_grid = ctk.CTkFrame(stats_frame)
        stats_grid.pack(fill="x", padx=10, pady=10)
        
        # Total Records
        total_frame = ctk.CTkFrame(stats_grid)
        total_frame.pack(side="left", fill="x", expand=True, padx=(0, 5))
        ctk.CTkLabel(total_frame, text="Total Records", font=ctk.CTkFont(size=12, weight="bold")).pack()
        self.total_records_label = ctk.CTkLabel(total_frame, text="0", font=ctk.CTkFont(size=20, weight="bold"))
        self.total_records_label.pack()
        
        # Check-ins
        checkin_frame = ctk.CTkFrame(stats_grid)
        checkin_frame.pack(side="left", fill="x", expand=True, padx=5)
        ctk.CTkLabel(checkin_frame, text="Check-ins", font=ctk.CTkFont(size=12, weight="bold")).pack()
        self.checkin_count_label = ctk.CTkLabel(checkin_frame, text="0", font=ctk.CTkFont(size=20, weight="bold"))
        self.checkin_count_label.pack()
        
        # Check-outs
        checkout_frame = ctk.CTkFrame(stats_grid)
        checkout_frame.pack(side="left", fill="x", expand=True, padx=5)
        ctk.CTkLabel(checkout_frame, text="Check-outs", font=ctk.CTkFont(size=12, weight="bold")).pack()
        self.checkout_count_label = ctk.CTkLabel(checkout_frame, text="0", font=ctk.CTkFont(size=20, weight="bold"))
        self.checkout_count_label.pack()
        
        # Active Users
        active_frame = ctk.CTkFrame(stats_grid)
        active_frame.pack(side="left", fill="x", expand=True, padx=(5, 0))
        ctk.CTkLabel(active_frame, text="Active Users", font=ctk.CTkFont(size=12, weight="bold")).pack()
        self.active_users_label = ctk.CTkLabel(active_frame, text="0", font=ctk.CTkFont(size=20, weight="bold"))
        self.active_users_label.pack()
        
        # Attendance table
        table_frame = ctk.CTkFrame(reports_window)
        table_frame.pack(fill="both", expand=True, padx=20, pady=(0, 20))
        
        # Table title
        table_title = ctk.CTkLabel(table_frame, text="Attendance Records", 
                                  font=ctk.CTkFont(size=16, weight="bold"))
        table_title.pack(pady=(10, 5))
        
        # Table description
        table_desc = ctk.CTkLabel(table_frame, text="Showing records from selected date range", 
                                 font=ctk.CTkFont(size=12))
        table_desc.pack(pady=(0, 10))
        
        # Attendance treeview
        attendance_columns = ("Timestamp", "User ID", "User Name", "Status")
        self.attendance_tree = ttk.Treeview(table_frame, columns=attendance_columns, show="headings", height=15)
        
        # Configure columns
        self.attendance_tree.heading("Timestamp", text="Timestamp")
        self.attendance_tree.heading("User ID", text="User ID")
        self.attendance_tree.heading("User Name", text="User Name")
        self.attendance_tree.heading("Status", text="Status")
        
        # Set column widths
        self.attendance_tree.column("Timestamp", width=150)
        self.attendance_tree.column("User ID", width=100)
        self.attendance_tree.column("User Name", width=150)
        self.attendance_tree.column("Status", width=100)
        
        # Add scrollbar
        attendance_scrollbar = ttk.Scrollbar(table_frame, orient="vertical", command=self.attendance_tree.yview)
        self.attendance_tree.configure(yscrollcommand=attendance_scrollbar.set)
        
        self.attendance_tree.pack(side="left", fill="both", expand=True, padx=(10, 0))
        attendance_scrollbar.pack(side="right", fill="y", padx=(0, 10))
        
        # Load initial data
        self.load_attendance_table()
        
        # Load statistics from backend
        self.load_statistics_from_backend()
        
        # Button frame
        button_frame = ctk.CTkFrame(reports_window)
        button_frame.pack(pady=20)
        
        # Refresh button
        refresh_button = ctk.CTkButton(button_frame, text="Refresh Data", 
                                     command=self.refresh_reports_data, width=120)
        refresh_button.pack(side="left", padx=(0, 10))
        
        # Close button
        close_button = ctk.CTkButton(button_frame, text="Close", 
                                   command=reports_window.destroy, width=120)
        close_button.pack(side="left")
        
        
    def get_first_day_of_month(self):
        """Get the first day of the current month"""
        today = datetime.now()
        first_day = today.replace(day=1)
        return first_day.strftime("%Y-%m-%d")
        
    def set_this_month_range(self):
        """Set date range to current month"""
        self.start_date_var.set(self.get_first_day_of_month())
        self.end_date_var.set(datetime.now().strftime("%Y-%m-%d"))
        self.load_attendance_table()
        
    def filter_attendance_data(self, event=None):
        """Filter attendance data based on search and date range"""
        self.load_attendance_table()
        
    def refresh_reports_data(self):
        """Refresh reports data from local file"""
        try:
            # Reload attendance data
            self.load_attendance_data()
            
            # Reload table and statistics
            self.load_attendance_table()
            self.load_statistics_from_backend()
            
            print("✅ Reports data refreshed")
        except Exception as e:
            print(f"Error refreshing reports data: {e}")
        
    def load_attendance_table(self):
        """Load attendance data into the table"""
        try:
            # Clear existing data
            for item in self.attendance_tree.get_children():
                self.attendance_tree.delete(item)
                
            # Get filter values
            start_date = self.start_date_var.get()
            end_date = self.end_date_var.get()
            search_term = self.search_var.get().lower()
            
            # Filter data
            filtered_data = []
            for record in self.attendance_log:
                timestamp = record.get('Timestamp', '')
                if not timestamp:
                    continue
                    
                # Extract date from timestamp
                try:
                    record_date = timestamp.split(' ')[0]  # Get date part (YYYY-MM-DD)
                except:
                    continue
                
                # Date range filter
                if start_date and end_date:
                    if not (start_date <= record_date <= end_date):
                        continue
                
                # Search filter
                if search_term:
                    user_id = record.get('User ID', '').lower()
                    user_name = record.get('User Name', '').lower()
                    if search_term not in user_id and search_term not in user_name:
                        continue
                
                filtered_data.append(record)
            
            # Add to table with proper formatting
            for record in filtered_data:
                timestamp = record.get('Timestamp', '')
                user_id = record.get('User ID', '')
                user_name = record.get('User Name', '')
                status = record.get('Status', '')
                
                # Format timestamp for display
                try:
                    dt = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S")
                    formatted_time = dt.strftime("%Y-%m-%d %H:%M:%S")
                except:
                    formatted_time = timestamp
                
                # Add to table
                item = self.attendance_tree.insert("", "end", values=(
                    formatted_time,
                    user_id,
                    user_name,
                    status
                ))
                
                # Apply color coding
                if status == "Check-in":
                    self.attendance_tree.item(item, tags=("checkin",))
                elif status == "Check-out":
                    self.attendance_tree.item(item, tags=("checkout",))
            
            # Configure tags for color coding
            self.attendance_tree.tag_configure("checkin", foreground="green")
            self.attendance_tree.tag_configure("checkout", foreground="red")
            
            # Update statistics
            self.update_statistics(filtered_data)
            
            print(f"✅ Loaded {len(filtered_data)} attendance records into table")
            
        except Exception as e:
            print(f"Error loading attendance table: {e}")
            import traceback
            traceback.print_exc()
            
    def load_statistics_from_backend(self):
        """Load statistics from local data (same as backend logic)"""
        try:
            # Calculate statistics from local attendance data
            today = datetime.now().strftime("%Y-%m-%d")
            
            total_records = len(self.attendance_log)
            checkins = len([r for r in self.attendance_log if r.get('Status') == 'Check-in'])
            checkouts = len([r for r in self.attendance_log if r.get('Status') == 'Check-out'])
            
            # Count unique users
            unique_users = set()
            for record in self.attendance_log:
                user_id = record.get('User ID', '')
                if user_id:
                    unique_users.add(user_id)
            active_users = len(unique_users)
            
            # Update labels
            self.total_records_label.configure(text=str(total_records))
            self.checkin_count_label.configure(text=str(checkins))
            self.checkout_count_label.configure(text=str(checkouts))
            self.active_users_label.configure(text=str(active_users))
            
            print(f"✅ Loaded statistics: {total_records} total, {checkins} check-ins, {checkouts} check-outs, {active_users} users")
            
        except Exception as e:
            print(f"Error loading statistics: {e}")
            
    def update_statistics(self, data):
        """Update statistics labels"""
        try:
            total_records = len(data)
            checkins = len([r for r in data if r.get('Status') == 'Check-in'])
            checkouts = len([r for r in data if r.get('Status') == 'Check-out'])
            
            # Count unique users
            unique_users = set()
            for record in data:
                user_id = record.get('User ID', '')
                if user_id:
                    unique_users.add(user_id)
            active_users = len(unique_users)
            
            # Update labels
            self.total_records_label.configure(text=str(total_records))
            self.checkin_count_label.configure(text=str(checkins))
            self.checkout_count_label.configure(text=str(checkouts))
            self.active_users_label.configure(text=str(active_users))
            
        except Exception as e:
            print(f"Error updating statistics: {e}")
            
    def on_threshold_change(self, value):
        """Handle threshold slider change"""
        self.confidence_threshold = round(value, 2)
        self.threshold_label.configure(text=f"Current: {self.confidence_threshold:.2f}")
        
    def save_settings(self):
        """Save current settings"""
        settings = {
            'confidence_threshold': self.confidence_threshold,
            'current_camera_index': self.current_camera_index
        }
        
        try:
            with open("app_settings.json", "w") as f:
                json.dump(settings, f, indent=2)
            messagebox.showinfo("Success", "Settings saved successfully!")
        except Exception as e:
            messagebox.showerror("Error", f"Failed to save settings: {e}")
            
    def start_camera(self):
        """Initialize and start the camera"""
        try:
            self.cap = cv2.VideoCapture(self.current_camera_index)
            if not self.cap.isOpened():
                messagebox.showerror("Error", f"Could not open camera {self.current_camera_index}")
                return
                
            # Set camera resolution
            self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
            self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
            
            print(f"✅ Camera {self.current_camera_index} started successfully")
            
        except Exception as e:
            messagebox.showerror("Error", f"Failed to start camera {self.current_camera_index}: {e}")
            
    def start_recognition(self):
        """Start face recognition loop"""
        if not self.is_running:
            self.is_running = True
            self.start_button.configure(state="disabled")
            self.stop_button.configure(state="normal")
            self.status_label.configure(text="Status: Recognition Active")
            
            # Start recognition thread
            self.recognition_thread = threading.Thread(target=self.recognition_loop, daemon=True)
            self.recognition_thread.start()
            
    def stop_recognition(self):
        """Stop face recognition loop"""
        self.is_running = False
        self.start_button.configure(state="normal")
        self.stop_button.configure(state="disabled")
        self.status_label.configure(text="Status: Stopped")
        
    def recognition_loop(self):
        """Main recognition loop with attendance logic"""
        while self.is_running:
            try:
                ret, frame = self.cap.read()
                if not ret:
                    time.sleep(0.01)
                    continue
                
                faces = self.face_app.get(frame)
                
                for face in faces:
                    bbox = face.bbox.astype(int)
                    cv2.rectangle(frame, (bbox[0], bbox[1]), (bbox[2], bbox[3]), (0, 255, 0), 2)
                    
                    if self.known_face_embeddings:
                        embedding = face.normed_embedding
                        
                        # Cosine similarity calculation
                        sims = np.dot(self.known_face_embeddings, embedding.T)
                        
                        best_match_index = np.argmax(sims)
                        confidence = sims[best_match_index]
                        
                        name = "Unknown"
                        if confidence > self.confidence_threshold:
                            name, user_id = self.known_face_names[best_match_index]
                            user_details = self.handle_attendance(user_id, name, confidence, frame)
                            if user_details:
                                self.add_to_history(user_details)
                                name = user_details.get('name', name)

                        display_text = f"{name} ({confidence:.2f})"
                        cv2.putText(frame, display_text, (bbox[0], bbox[1] - 10), 
                                    cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)

                # Convert to PhotoImage for display
                frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                frame_pil = Image.fromarray(frame_rgb)
                frame_tk = ImageTk.PhotoImage(frame_pil)
                
                # Update camera label
                self.camera_label.configure(image=frame_tk, text="")
                self.camera_label.image = frame_tk
                
            except Exception as e:
                print(f"Error in recognition loop: {e}")
                time.sleep(0.5)
                
    def handle_attendance(self, user_id, user_name, confidence, frame):
        """Handle attendance using web backend endpoint"""
        try:
            _, buffer = cv2.imencode('.jpg', frame)
            frame_base64 = base64.b64encode(buffer).decode('utf-8')
            
            data = {'image': f'data:image/jpeg;base64,{frame_base64}', 'userId': str(user_id)}
            
            response = requests.post(f"{self.backend_url}/recognize", json=data, timeout=10)
            
            if response.status_code == 200:
                result = response.json()
                if result.get('success'):
                    user_details = {
                        'name': result.get('user_name', user_name),
                        'department': result.get('user_department', ''),
                        'attendance_status': result.get('attendance_status', 'Unknown'),
                        'timestamp': result.get('timestamp', ''),
                        'confidence': confidence * 100 # Convert to percentage for consistency
                    }
                    print(f"✅ Attendance saved via backend: {result.get('message', '')}")
                    return user_details
                else:
                    print(f"⚠️ Backend recognition failed: {result.get('message', 'Unknown error')}")
            else:
                print(f"❌ Backend API error: {response.status_code} - {response.text}")
            return None
                
        except requests.exceptions.RequestException as e:
            print(f"❌ Network error calling backend: {e}")
            return None
        except Exception as e:
            print(f"❌ Error handling attendance: {e}")
            return None
            
    def add_to_history(self, user_details):
        """Add recognition result to history with color coding"""
        try:
            timestamp = user_details.get('timestamp', '')
            time_str = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S").strftime("%H:%M:%S") if timestamp else ""
            
            user_name = user_details.get('name', 'Unknown')
            team = user_details.get('department', 'Unknown')
            confidence = user_details.get('confidence', 0)
            attendance_status = user_details.get('attendance_status', 'Unknown')
            
            item = self.history_tree.insert("", 0, values=(
                time_str, user_name, team, f"{confidence:.1f}%", attendance_status
            ))
            
            tag = "checkin" if attendance_status == "Check-in" else "checkout"
            self.history_tree.item(item, tags=(tag,))
            self.history_tree.tag_configure("checkin", foreground="green")
            self.history_tree.tag_configure("checkout", foreground="red")
            
            if len(self.history_tree.get_children()) > 100:
                self.history_tree.delete(self.history_tree.get_children()[-1])
                
        except Exception as e:
            print(f"Error adding to history: {e}")
            
    def clear_history(self):
        """Clear recognition history"""
        for item in self.history_tree.get_children():
            self.history_tree.delete(item)
            
    def get_available_cameras(self):
        """Get list of available cameras with meaningful names"""
        available_cameras = []
        for i in range(10):
            cap = cv2.VideoCapture(i)
            if cap.isOpened():
                width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
                height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
                available_cameras.append(self.get_camera_display_name(i, width, height))
                cap.release()
        return available_cameras
        
    def get_camera_display_name(self, index, width, height):
        """Get a meaningful display name for the camera"""
        return f"Camera {index} ({width}x{height})"
            
    def on_camera_change(self, camera_selection):
        """Handle camera selection change"""
        try:
            camera_index = int(camera_selection.split(' ')[1])
            if camera_index != self.current_camera_index:
                if self.cap and self.cap.isOpened():
                    self.cap.release()
                self.current_camera_index = camera_index
                self.start_camera()
                self.status_label.configure(text=f"Status: Switched to Camera {camera_index}")
        except Exception as e:
            print(f"Error changing camera: {e}")
            
    def on_closing(self):
        """Handle application closing"""
        self.is_running = False
        if self.cap:
            self.cap.release()
        self.root.destroy()
        
    def run(self):
        """Run the application"""
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.root.mainloop()

if __name__ == "__main__":
    app = FaceRecognitionApp()
    app.run()
