import cv2
import torch
import numpy as np
from facenet_pytorch import InceptionResnetV1, MTCNN
import os
import tkinter as tk
from tkinter import messagebox
from PIL import Image, ImageTk
import time
import threading # Necessário para rodar a reprodução do áudio em segundo plano

# === CONFIGURAÇÕES GERAIS ===
DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'
EMB_FILE = 'classifier/usuarios_centróides.npy'
IMAGE_SIZE = 160
MARGIN = 10
TIMEOUT_SEGUNDOS = 5 # Tempo limite para o reconhecimento

# VALORES DE CALIBRAÇÃO
TOLERANCIA = 1.0
LIMITE_CONFIANCA = 85   
LIMITE_LOGIN_MANUAL = 10 

# --- CONFIGURAÇÃO TEXT-TO-SPEECH (TTS) ---
TTS_ENABLED = False
try:
    import pyttsx3
    TTS_ENABLED = True
except ImportError:
    print("AVISO: pyttsx3 não encontrado.")
    print("Para habilitar o áudio (TTS local), instale: 'pip install pyttsx3'")

# --- Inicialização de Modelos ---
try:
    print("Carregando modelos FaceNet e MTCNN...")
    mtcnn = MTCNN(
        image_size=IMAGE_SIZE, 
        margin=MARGIN, 
        min_face_size=20, 
        thresholds=[0.6, 0.7, 0.7], 
        factor=0.709, 
        post_process=True, 
        device=DEVICE
    )
    resnet = InceptionResnetV1(pretrained='vggface2').eval().to(DEVICE)
    print("Modelos carregados.")

    if not os.path.exists(EMB_FILE):
        raise FileNotFoundError(f"Arquivo de centróides '{EMB_FILE}' não encontrado! Execute o treinamento primeiro.")
    usuarios = np.load(EMB_FILE, allow_pickle=True).item()
    
except Exception as e:
    messagebox.showerror("Erro Crítico", f"Falha ao carregar modelos/embeddings: {e}")
    exit()

# === FUNÇÃO DE CONFIANÇA ===
def dist_to_confidence(dist, threshold=TOLERANCIA):
    if dist < threshold:
        conf = 100 * (1 - (dist / threshold))
        return max(0, min(100, conf))
    else:
        return 0.0

# === CLASSE PRINCIPAL DO TKINTER ===
class AppReconhecimento:
    def __init__(self, master):
        self.master = master
        master.title("Sistema de Reconhecimento Facial")

        # --- Variáveis de Estado ---
        self.usuario_logado = None
        self.login_time = None # Para registrar o início do login manual
        self._last_tts_time = 0 # Inicialização para controle de taxa de TTS de alerta
        
        # --- Configuração da Câmera ---
        self.cap = cv2.VideoCapture(1, cv2.CAP_DSHOW) 
        self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
        self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
        
        if not self.cap.isOpened():
            messagebox.showerror("Erro Câmera", "Não foi possível abrir a câmera. Verifique o índice (0 ou 1).")
            master.destroy()
            return
            
        # --- Interface (Formulário) ---
        self.frame_login = tk.Frame(master)
        self.frame_login.pack(padx=10, pady=10)

        tk.Label(self.frame_login, text="Usuário:", font=('Arial', 12)).grid(row=0, column=0, padx=5, pady=5)
        self.entry_user = tk.Entry(self.frame_login, font=('Arial', 12), width=20)
        self.entry_user.grid(row=0, column=1, padx=5, pady=5)

        self.btn_login = tk.Button(self.frame_login, text="Fazer Login", command=self.fazer_login, font=('Arial', 12, 'bold'), bg='lightblue')
        self.btn_login.grid(row=1, column=0, columnspan=2, pady=10)

        # --- Área de Visualização da Câmera ---
        self.label_camera = tk.Label(master)
        self.label_camera.pack(pady=10)
        
        # --- Área de Status ---
        self.status_var = tk.StringVar(value="Status: Aguardando Login")
        self.label_status = tk.Label(master, textvariable=self.status_var, font=('Arial', 14, 'bold'), fg='blue')
        self.label_status.pack(pady=5)
        
        self.update_camera()

    def falar_login_tts(self, mensagem):
        """Gera e reproduz uma mensagem de boas-vindas/login em uma thread separada."""
        if not TTS_ENABLED:
            return

        try:
            engine = pyttsx3.init()
            
            # Tenta encontrar uma voz em português para melhor pronúncia
            voices = engine.getProperty('voices')
            pt_voice = next((v.id for v in voices if 'portuguese' in v.name.lower() or 'brazil' in v.name.lower()), None)
            if pt_voice:
                engine.setProperty('voice', pt_voice)
                
            engine.say(mensagem)
            engine.runAndWait() 

        except Exception as e:
            print(f"Erro ao gerar/reproduzir áudio com pyttsx3: {e}")

    def falar_alerta(self, mensagem):
        """Gera e reproduz uma mensagem de alerta/erro em uma thread separada."""
        if not TTS_ENABLED:
            return

        try:
            engine = pyttsx3.init()
            
            # Tenta encontrar uma voz em português para melhor pronúncia
            voices = engine.getProperty('voices')
            pt_voice = next((v.id for v in voices if 'portuguese' in v.name.lower() or 'brazil' in v.name.lower()), None)
            if pt_voice:
                engine.setProperty('voice', pt_voice)
                
            # Opcional: Configurar para um tom de alerta (ex: um pouco mais devagar)
            # rate = engine.getProperty('rate')
            # engine.setProperty('rate', rate - 30) 
            
            engine.say(mensagem)
            engine.runAndWait() 

        except Exception as e:
            print(f"Erro ao gerar/reproduzir áudio de alerta com pyttsx3: {e}")
            
    def fazer_login(self):
        """Lógica acionada pelo botão Fazer Login."""
        nome_input = self.entry_user.get().strip()
        
        if not nome_input:
            messagebox.showwarning("Atenção", "Por favor, digite o nome do usuário.")
            return

        if nome_input.lower() in [u.lower() for u in usuarios.keys()]:
            self.usuario_logado = nome_input
            self.login_time = time.time() # Registra o tempo de início do login
            self.status_var.set(f"Modo Login Ativo para '{nome_input}'. TEMPO: {TIMEOUT_SEGUNDOS}s")
            self.label_status.config(fg='orange')
            print(f"Modo Login Manual Ativo para: {self.usuario_logado}")
        else:
            messagebox.showerror("Erro", f"Usuário '{nome_input}' não encontrado na base de dados.")
            self.usuario_logado = None
            self.login_time = None
            self.status_var.set("Status: Usuário não encontrado. Tente novamente.")
            self.label_status.config(fg='red')

    def falar_boas_vindas(self, nome):
        """Função antiga, mantida por compatibilidade e delegando para falar_login_tts."""
        mensagem = f"Seja bem-vindo, {nome}. Acesso autorizado."
        self.falar_login_tts(mensagem)
            
    def update_camera(self):
        """Atualiza o frame da câmera, executa o reconhecimento e a lógica de timeout/guia oval."""
        ret, frame = self.cap.read()
        if not ret:
            self.master.after(10, self.update_camera)
            return

        display_frame = frame.copy()
        
        # Dimensões do Frame (para centralizar o oval)
        H, W = frame.shape[:2]
        
        
        if self.usuario_logado:
            
            # === DESENHA GUIA OVAL ===
            center_coords = (W // 2, H // 2)
            axes_lengths = (W // 4, H // 3) 
            cv2.ellipse(display_frame, center_coords, axes_lengths, 0, 0, 360, (255, 255, 0), 2) # Amarelo/Ciano
            
            # === LÓGICA DE TIMEOUT ===
            tempo_decorrido = time.time() - self.login_time
            tempo_restante = max(0, TIMEOUT_SEGUNDOS - tempo_decorrido)
            
            # Exibe o tempo restante
            cv2.putText(display_frame, f"Tempo: {tempo_restante:.1f}s", (W - 150, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
            
            if tempo_decorrido > TIMEOUT_SEGUNDOS:
                self.status_var.set(f"Acesso NÃO AUTORIZADO para {self.usuario_logado}: Tempo Esgotado.")
                self.label_status.config(fg='red')
                
                # NOVO: TTS para tempo esgotado
                mensagem_alerta = f"Tempo limite esgotado. Acesso não autorizado para {self.usuario_logado}."
                threading.Thread(target=self.falar_alerta, args=(mensagem_alerta,)).start()
                
                messagebox.showerror("Acesso Negado", f"Acesso não autorizado para {self.usuario_logado}: Tempo limite de 5 segundos esgotado.")
                self.usuario_logado = None # Desativa o modo login
                self.login_time = None
            else:
                # Lógica de Reconhecimento (só executa se o tempo não esgotou)
                boxes, probs = mtcnn.detect(frame)
                
                if boxes is not None and len(boxes) == 1:
                    box = boxes[0]
                    x1, y1, x2, y2 = [int(b) for b in box]
                    
                    try:
                        face_crop = frame[y1:y2, x1:x2]
                        face_tensor = mtcnn(face_crop) 
                        
                        if face_tensor is not None:
                            embedding = resnet(face_tensor.to(DEVICE).unsqueeze(0)).detach().cpu().numpy().flatten()
                            
                            melhor_nome = "Desconhecido"
                            menor_dist = float('inf')
                            
                            for nome_usu, centróide in usuarios.items():
                                dist = np.linalg.norm(centróide.flatten() - embedding)
                                if dist < menor_dist:
                                    menor_dist = dist
                                    melhor_nome = nome_usu

                            confianca = dist_to_confidence(menor_dist)

                            # LÓGICA DE LOGIN MANUAL
                            if melhor_nome.lower() == self.usuario_logado.lower() and confianca >= LIMITE_LOGIN_MANUAL:
                                # ACESSO AUTORIZADO
                                self.status_var.set(f"ACESSO AUTORIZADO! {melhor_nome} ({confianca:.1f}%)")
                                self.label_status.config(fg='green')
                                
                                login_nome = self.usuario_logado
                                self.usuario_logado = None 
                                self.login_time = None
                                self.entry_user.delete(0, tk.END) 
                                
                                # NOVO: Reproduz a mensagem de boas-vindas em uma thread separada
                                threading.Thread(target=self.falar_boas_vindas, args=(login_nome,)).start()
                                
                                self.master.update()
                                messagebox.showinfo("Acesso", f"Acesso Autorizado para {login_nome}!")
                            else:
                                # NEGADO (Aguardando)
                                self.status_var.set(f"Login Falhou: {melhor_nome} ({confianca:.1f}%) - Min: {LIMITE_LOGIN_MANUAL}%")
                                self.label_status.config(fg='red')
                                
                                # NOVO: Alerta de rosto incorreto (com limitação de taxa)
                                if melhor_nome.lower() != self.usuario_logado.lower() and confianca >= LIMITE_LOGIN_MANUAL and TTS_ENABLED:
                                    if (time.time() - self._last_tts_time) > 3.0: # Limita o alerta a cada 3 segundos
                                        mensagem_alerta = f"Rosto do usuario não detectado. Por favor, {self.usuario_logado}, posicione seu rosto em frente à câmera."
                                        threading.Thread(target=self.falar_alerta, args=(mensagem_alerta,)).start()
                                        self._last_tts_time = time.time()

                            cor_rec = (0, 255, 0) if self.usuario_logado is None else (255, 100, 0)
                            cv2.rectangle(display_frame, (x1, y1), (x2, y2), cor_rec, 2)
                            cv2.putText(display_frame, f"{melhor_nome} {confianca:.1f}%", (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, cor_rec, 2)
                    
                    except Exception as e:
                        print(f"Erro no processamento da face: {e}")
                        self.status_var.set("Erro no Processamento do Rosto.")
                        self.label_status.config(fg='red')
                
                elif boxes is not None and len(boxes) > 1:
                    self.status_var.set("Múltiplos rostos detectados! Aproxime-se sozinho.")
                    self.label_status.config(fg='red')
                    for box in boxes:
                        x1, y1, x2, y2 = [int(b) for b in box]
                        cv2.rectangle(display_frame, (x1, y1), (x2, y2), (0, 0, 255), 2)
                
                else:
                    self.status_var.set(f"Modo Login Ativo para '{self.usuario_logado}'. Posicione o rosto no oval...")
                    self.label_status.config(fg='orange')
            
        else:
            # MODO AUTOMÁTICO (Sem login manual ativo)
            self.status_var.set("Status: Aguardando Login")
            self.label_status.config(fg='blue')

        # Converte o frame OpenCV para o formato Tkinter
        cv2image = cv2.cvtColor(display_frame, cv2.COLOR_BGR2RGBA)
        img = Image.fromarray(cv2image)
        imgtk = ImageTk.PhotoImage(image=img)
        self.label_camera.imgtk = imgtk
        self.label_camera.configure(image=imgtk)

        # Chama esta função novamente após 10ms
        self.master.after(10, self.update_camera)

    def on_closing(self):
        """Fecha a câmera e a janela ao fechar o Tkinter."""
        if messagebox.askokcancel("Sair", "Tem certeza que deseja sair?"):
            self.cap.release()
            self.master.destroy()

if __name__ == "__main__":
    root = tk.Tk()
    app = AppReconhecimento(root)
    root.protocol("WM_DELETE_WINDOW", app.on_closing)
    root.mainloop()
