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

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

class FaceRecognitionApp:
    def __init__(self):
        self.root = ctk.CTk()
        self.root.title("Enhanced Face Recognition System")
        self.root.geometry("1400x900")
        
        # Initialize variables
        self.is_running = False
        self.cap = None
        self.face_cascade = None
        self.recognizer = None
        self.user_data = {}
        self.current_camera_index = 0
        
        # Settings variables
        self.confidence_threshold = 120  # Lower is better for LBPH
        self.detection_sensitivity = 1.1
        self.min_neighbors = 4
        self.min_size = (30, 30)
        
        # 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 face detection and recognition models"""
        try:
            # Load face cascade
            cascade_path = "haarcascade_frontalface_default.xml"
            if not os.path.exists(cascade_path):
                messagebox.showerror("Error", f"Face cascade file not found: {cascade_path}")
                return
            self.face_cascade = cv2.CascadeClassifier(cascade_path)
            
            # Load face recognizer
            recognizer_path = "face_recognizer.yml"
            if not os.path.exists(recognizer_path):
                messagebox.showerror("Error", f"Face recognizer file not found: {recognizer_path}")
                return
            self.recognizer = cv2.face.LBPHFaceRecognizer_create()
            self.recognizer.read(recognizer_path)
            
            # 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")
                
        except Exception as e:
            messagebox.showerror("Error", f"Failed to load models: {e}")
            
    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("500x600")
        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=str(self.current_camera_index))
            camera_dropdown = ctk.CTkOptionMenu(camera_frame, 
                                              values=available_cameras,
                                              variable=camera_var,
                                              command=self.on_camera_change)
            camera_dropdown.pack(pady=5)
            
            # Camera info
            camera_info = ctk.CTkLabel(camera_frame, 
                                     text=f"Current: {self.get_camera_display_name(self.current_camera_index, 0, 0)}",
                                     font=ctk.CTkFont(size=12))
            camera_info.pack(pady=5)
            
            # Refresh cameras button
            refresh_button = ctk.CTkButton(camera_frame, text="Refresh Cameras", 
                                        command=self.refresh_cameras)
            refresh_button.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)
        self.confidence_slider = ctk.CTkSlider(confidence_frame, from_=50, to=200, 
                                            number_of_steps=150, 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))
        
        # Detection sensitivity
        sensitivity_frame = ctk.CTkFrame(settings_window)
        sensitivity_frame.pack(fill="x", padx=20, pady=10)
        
        ctk.CTkLabel(sensitivity_frame, text="Detection Sensitivity:", 
                    font=ctk.CTkFont(size=14, weight="bold")).pack(pady=5)
        self.sensitivity_slider = ctk.CTkSlider(sensitivity_frame, from_=1.0, to=2.0, 
                                              number_of_steps=100, command=self.on_sensitivity_change)
        self.sensitivity_slider.set(self.detection_sensitivity)
        self.sensitivity_slider.pack(pady=5)
        
        self.sensitivity_label = ctk.CTkLabel(sensitivity_frame, text=f"Current: {self.detection_sensitivity}")
        self.sensitivity_label.pack(pady=(0, 5))
        
        # Min neighbors
        neighbors_frame = ctk.CTkFrame(settings_window)
        neighbors_frame.pack(fill="x", padx=20, pady=10)
        
        ctk.CTkLabel(neighbors_frame, text="Min Neighbors:", 
                    font=ctk.CTkFont(size=14, weight="bold")).pack(pady=5)
        self.neighbors_slider = ctk.CTkSlider(neighbors_frame, from_=1, to=10, 
                                            number_of_steps=9, command=self.on_neighbors_change)
        self.neighbors_slider.set(self.min_neighbors)
        self.neighbors_slider.pack(pady=5)
        
        self.neighbors_label = ctk.CTkLabel(neighbors_frame, text=f"Current: {self.min_neighbors}")
        self.neighbors_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"""
        while self.is_running:
            try:
                ret, frame = self.cap.read()
                if not ret:
                    continue
                    
                # Convert to RGB for display
                frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                
                # Detect faces with current sensitivity settings
                gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
                faces = self.face_cascade.detectMultiScale(
                    gray, 
                    scaleFactor=self.detection_sensitivity, 
                    minNeighbors=self.min_neighbors,
                    minSize=self.min_size
                )
                
                # Draw rectangles around detected faces
                for (x, y, w, h) in faces:
                    cv2.rectangle(frame_rgb, (x, y), (x+w, y+h), (0, 255, 0), 2)
                    
                    # Add corner indicators
                    corner_size = 20
                    # Top-left corner
                    cv2.line(frame_rgb, (x, y), (x + corner_size, y), (0, 255, 0), 3)
                    cv2.line(frame_rgb, (x, y), (x, y + corner_size), (0, 255, 0), 3)
                    # Top-right corner
                    cv2.line(frame_rgb, (x + w - corner_size, y), (x + w, y), (0, 255, 0), 3)
                    cv2.line(frame_rgb, (x + w, y), (x + w, y + corner_size), (0, 255, 0), 3)
                    # Bottom-left corner
                    cv2.line(frame_rgb, (x, y + h - corner_size), (x, y + h), (0, 255, 0), 3)
                    cv2.line(frame_rgb, (x, y + h), (x + corner_size, y + h), (0, 255, 0), 3)
                    # Bottom-right corner
                    cv2.line(frame_rgb, (x + w - corner_size, y + h), (x + w, y + h), (0, 255, 0), 3)
                    cv2.line(frame_rgb, (x + w, y + h - corner_size), (x + w, y + h), (0, 255, 0), 3)
                    
                    # Perform recognition on the first detected face
                    if self.recognizer and len(faces) > 0:
                        face_roi = gray[y:y+h, x:x+w]
                        face_roi = cv2.resize(face_roi, (200, 200))
                        face_roi = cv2.equalizeHist(face_roi)
                        
                        try:
                            # Predict
                            user_id, confidence = self.recognizer.predict(face_roi)
                            
                            # Get user name
                            user_name = self.user_data.get(str(user_id), {}).get('name', f"User {user_id}")
                            
                            # Add to history and handle attendance (only if confidence is good)
                            if confidence < self.confidence_threshold:
                                user_details = self.handle_attendance(user_id, user_name, confidence, frame)
                                if user_details:
                                    # Update display with proper name from backend
                                    display_name = user_details.get('name', user_name)
                                    team = user_details.get('department', '')
                                    
                                    # Add recognition info to frame
                                    text = f"{display_name} ({confidence:.1f})"
                                    if team:
                                        text += f" - {team}"
                                    cv2.putText(frame_rgb, text, (x, y-10), 
                                              cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
                                    
                                    self.add_to_history(user_details)
                            else:
                                # Add recognition info to frame for unrecognized faces
                                text = f"{user_name} ({confidence:.1f})"
                                cv2.putText(frame_rgb, text, (x, y-10), 
                                          cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
                            
                        except Exception as e:
                            print(f"Recognition error: {e}")
                
                # Convert to PhotoImage for display
                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
                
                # Control frame rate
                cv2.waitKey(30)
                
            except Exception as e:
                print(f"Error in recognition loop: {e}")
                continue
                
    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': result.get('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()