#!/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 face_recognition

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

class FaceRecognitionApp:
    def __init__(self):
        self.root = ctk.CTk()
        self.root.title("Enhanced Face Recognition System v3.0 - Advanced (face_recognition)")
        self.root.geometry("1400x900")
        
        # Initialize variables
        self.is_running = False
        self.cap = None
        self.user_data = {}
        self.current_camera_index = 0
        
        # Face recognition variables
        self.known_face_encodings = []
        self.known_face_names = []
        
        # Settings variables
        self.confidence_threshold = 60  # Higher is better (percentage)
        
        # Backend endpoint configuration
        self.backend_url = "http://localhost:5001"
        
        # Attendance tracking
        self.attendance_log = []
        self.today_attendance = {}  # Track today's check-ins/check-outs per user
        
        # 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 user data and face encodings"""
        try:
            # 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 encodings
            self.load_face_encodings()

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

    def load_face_encodings(self):
        """Load face encodings from registered faces"""
            self.known_face_encodings = []
            self.known_face_names = []
        registered_faces_path = "registered_faces"
            
        if not os.path.exists(registered_faces_path):
            messagebox.showwarning("Warning", "registered_faces directory not found.")
                return
            
        for user_id in os.listdir(registered_faces_path):
            user_path = os.path.join(registered_faces_path, user_id)
            if os.path.isdir(user_path):
                user_name = self.user_data.get(user_id, {}).get('name', f"User {user_id}")
                image_files = [f for f in os.listdir(user_path) if f.lower().endswith(('.png', '.jpg', '.jpeg'))]
                if not image_files:
                    continue
                
                image_path = os.path.join(user_path, image_files[0])
                try:
                    image = face_recognition.load_image_file(image_path)
                    encodings = face_recognition.face_encodings(image)
                    if encodings:
                        self.known_face_encodings.append(encodings[0])
                        self.known_face_names.append((user_name, user_id))
                        print(f"✅ Encoded {image_files[0]} for {user_name}")
                            except Exception as e:
                    print(f"⚠️ Could not process {image_path}: {e}")
        
        if not self.known_face_encodings:
            print("⚠️ No face encodings loaded. Recognition will not work.")
            messagebox.showwarning("Warning", "No faces encoded. Please register faces.")
        else:
            print(f"✅ Loaded {len(self.known_face_encodings)} face encodings.")
            
    def load_attendance_data(self):
        """Load attendance data from local CSV file (backend integration handled separately)"""
        try:
            # Load from local CSV file (same file that backend uses)
            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:
                print("⚠️ No attendance_log.csv found")
                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", 
                                 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") # Reduced height
        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)
        
        # Get available cameras
        available_cameras = self.get_available_cameras()
        
        if available_cameras:
            # Camera dropdown
            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", 
                        font=ctk.CTkFont(size=12)).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 (%):", 
                    font=ctk.CTkFont(size=14, weight="bold")).pack(pady=5)
        ctk.CTkLabel(confidence_frame, text="Higher is better. Rejects matches below this value.",
                    font=ctk.CTkFont(size=10)).pack(pady=(0,5))
        self.confidence_slider = ctk.CTkSlider(confidence_frame, from_=40, to=100, 
                                            number_of_steps=60, 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}%")
        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 = int(value)
        self.threshold_label.configure(text=f"Current: {self.confidence_threshold}%")
        
    def on_sensitivity_change(self, value):
        """Handle sensitivity slider change"""
        self.detection_sensitivity = round(value, 2)
        self.sensitivity_label.configure(text=f"Current: {self.detection_sensitivity}")
        
    def on_neighbors_change(self, value):
        """Handle neighbors slider change"""
        self.min_neighbors = int(value)
        self.neighbors_label.configure(text=f"Current: {self.min_neighbors}")
        
    def save_settings(self):
        """Save current settings"""
        settings = {
            'confidence_threshold': self.confidence_threshold,
            'detection_sensitivity': self.detection_sensitivity,
            'min_neighbors': self.min_neighbors,
            '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"""
        process_this_frame = True
        while self.is_running:
            try:
                ret, frame = self.cap.read()
                if not ret:
                    time.sleep(0.1)
                    continue
                
                # Resize frame for faster processing
                small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25)
                # Convert the image from BGR color (which OpenCV uses) to RGB color (which face_recognition uses)
                rgb_small_frame = cv2.cvtColor(small_frame, cv2.COLOR_BGR2RGB)
                
                face_locations = []
                face_encodings = []
                face_names = []

                if process_this_frame:
                    # Find all the faces and face encodings in the current frame of video
                face_locations = face_recognition.face_locations(rgb_small_frame)
                face_encodings = face_recognition.face_encodings(rgb_small_frame, face_locations)
                
                    for face_encoding in face_encodings:
                        # See if the face is a match for the known face(s)
                        matches = face_recognition.compare_faces(self.known_face_encodings, face_encoding)
                        name = "Unknown"
                        user_id = "Unknown"

                        # Or instead, use the known face with the smallest distance to the new face
                    face_distances = face_recognition.face_distance(self.known_face_encodings, face_encoding)
                        if len(face_distances) == 0:
                            face_names.append(("Unknown", 0))
                            continue
                    
                    best_match_index = np.argmin(face_distances)
                    if matches[best_match_index]:
                            name, user_id = self.known_face_names[best_match_index]
                        
                        confidence = (1 - face_distances[best_match_index]) * 100
                        
                        if name != "Unknown" and confidence >= self.confidence_threshold:
                            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) # Use name from backend if available
                        
                        face_names.append((name, confidence))

                process_this_frame = not process_this_frame

                # Display the results
                for (top, right, bottom, left), (name, confidence) in zip(face_locations, face_names):
                    # Scale back up face locations since the frame we detected in was scaled to 1/4 size
                    top *= 4
                    right *= 4
                    bottom *= 4
                    left *= 4

                    # Draw a box around the face
                    cv2.rectangle(frame, (left, top), (right, bottom), (0, 255, 0), 2)

                    # Draw a label with a name below the face
                    cv2.rectangle(frame, (left, bottom - 35), (right, bottom), (0, 255, 0), cv2.FILLED)
                    font = cv2.FONT_HERSHEY_DUPLEX
                    display_text = f"{name} ({confidence:.1f}%)"
                    cv2.putText(frame, display_text, (left + 6, bottom - 6), font, 1.0, (255, 255, 255), 1)

                # 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}")
                import traceback
                traceback.print_exc()
                time.sleep(0.5)
                
    def handle_attendance(self, user_id, user_name, confidence, frame):
        """Handle attendance using web backend endpoint (same as web version)"""
        try:
            # Convert frame to base64 for API call
            _, buffer = cv2.imencode('.jpg', frame)
            frame_base64 = base64.b64encode(buffer).decode('utf-8')
            
            # Prepare data for API call
            data = {
                'image': f'data:image/jpeg;base64,{frame_base64}',
                'userId': str(user_id)
            }
            
            # Call the web backend recognize endpoint
            response = requests.post(f"{self.backend_url}/recognize", json=data, timeout=10)
            
            if response.status_code == 200:
                result = response.json()
                if result.get('success'):
                    attendance_status = result.get('attendance_status', 'Unknown')
                    message = result.get('message', '')
                    
                    # Get user details from backend response
                    user_details = {
                        'name': result.get('user_name', user_name),
                        'email': result.get('user_email', ''),
                        'department': result.get('user_department', ''),
                        'status': result.get('user_status', ''),
                        'attendance_status': attendance_status,
                        'timestamp': result.get('timestamp', ''),
                        'confidence': confidence
                    }
                    
                    print(f"✅ Attendance saved via backend: {message}")
                    return user_details
            else:
                    print(f"⚠️ Backend recognition failed: {result.get('message', 'Unknown error')}")
                    return None
            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:
            # Extract time from timestamp (format: "2025-09-05 18:30:45" -> "18:30:45")
            timestamp = user_details.get('timestamp', '')
            if timestamp:
                try:
                    dt = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S")
                    time_str = dt.strftime("%H:%M:%S")
                except:
                    time_str = datetime.now().strftime("%H:%M:%S")
                else:
                time_str = datetime.now().strftime("%H:%M:%S")
            
            # Get user details
            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')
            
            # Insert at the top
            item = self.history_tree.insert("", 0, values=(
                time_str, 
                user_name, 
                team, 
                f"{confidence:.1f}%", 
                attendance_status
            ))
            
            # Apply color coding based on attendance status
            if attendance_status == "Check-in":
                # Green for check-in
                self.history_tree.set(item, "Attendance", "✓ Check-in")
                # Note: ttk.Treeview doesn't support direct color changes, but we can use tags
                self.history_tree.item(item, tags=("checkin",))
            elif attendance_status == "Check-out":
                # Red for check-out
                self.history_tree.set(item, "Attendance", "✗ Check-out")
                self.history_tree.item(item, tags=("checkout",))
            
            # Configure tags for color coding
            self.history_tree.tag_configure("checkin", foreground="green")
            self.history_tree.tag_configure("checkout", foreground="red")
            
            # Limit history to 100 entries
            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 = []
        
        # Test cameras from index 0 to 10
        for i in range(10):
            cap = cv2.VideoCapture(i)
            if cap.isOpened():
                # Get camera properties
                width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
                height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
                
                # Try to get more detailed camera info
                camera_name = self.get_camera_display_name(i, width, height)
                
                available_cameras.append(camera_name)
                cap.release()
                
        return available_cameras
        
    def get_camera_display_name(self, index, width, height):
        """Get a meaningful display name for the camera"""
        try:
            # Try to get camera backend info
            backend = cv2.getBackendName()
            
            # Common camera patterns
            if index == 0:
                # Camera 0 is usually the default/built-in camera
                if width >= 1920 or height >= 1080:  # High resolution
                    return f"MacBook Built-in Camera ({width}x{height})"
                else:
                    return f"Built-in Camera ({width}x{height})"
            elif index == 1:
                # Camera 1 is often external devices
                if "AVFoundation" in backend or "Darwin" in backend:
                    return f"iPhone/External Camera ({width}x{height})"
                else:
                    return f"External Camera ({width}x{height})"
            else:
                # Other cameras
                return f"Camera {index} ({width}x{height})"
            
        except Exception as e:
            # Fallback to basic naming
            return f"Camera {index} ({width}x{height})"
            
    def on_camera_change(self, camera_selection):
        """Handle camera selection change"""
        try:
            # Extract camera index from selection (more robust parsing)
            camera_index = self.extract_camera_index(camera_selection)
            
            if camera_index is not None and camera_index != self.current_camera_index:
                # Stop current camera
                if self.cap and self.cap.isOpened():
                    self.cap.release()
                
                # Update camera index
                self.current_camera_index = camera_index
                
                # Restart camera with new selection
                self.start_camera()
                
                # Update status with camera name
                camera_name = self.get_camera_display_name(camera_index, 0, 0)
                self.status_label.configure(text=f"Status: Switched to {camera_name}")
            
        except Exception as e:
            print(f"Error changing camera: {e}")
            
    def extract_camera_index(self, camera_selection):
        """Extract camera index from camera selection string"""
        try:
            # Handle different formats: "Camera 0 (640x480)", "Built-in Camera (640x480)", etc.
            if "Camera" in camera_selection:
                # Extract number after "Camera"
                parts = camera_selection.split()
                for i, part in enumerate(parts):
                    if part == "Camera" and i + 1 < len(parts):
                        return int(parts[i + 1])
            elif "Built-in" in camera_selection:
                return 0  # Built-in is usually index 0
            elif "iPhone" in camera_selection or "External" in camera_selection:
                return 1  # External devices are usually index 1
                
            # Fallback: try to find any number in the string
            import re
            numbers = re.findall(r'\d+', camera_selection)
            if numbers:
                return int(numbers[0])
            
        except Exception as e:
            print(f"Error extracting camera index: {e}")
            
        return None
            
    def refresh_cameras(self):
        """Refresh available cameras list"""
        # This would typically refresh the camera dropdown
        # For now, just show a message
        messagebox.showinfo("Refresh", "Camera list refreshed. Close and reopen settings to see changes.")

    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()