"""
ICEBERG Manillas POS - Punto de entrada.

Una sola ventana fullscreen sin bordes (AppWindow) que gestiona todo el ciclo:
  login -> pantalla principal -> logout -> login

Restricciones:
  - El login bloquea hasta que el controlador USB (programador HID) este conectado.
  - Si el USB se desconecta durante una sesion activa, aparece una alerta grande
    en la parte superior de la pantalla.
"""
import sys
import os
import atexit
import threading
from datetime import datetime

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

import customtkinter as ctk

from ui.login_screen import LoginScreen
from ui.main_window import MainWindow
import api
import session
import sync_worker
import printing


class AppWindow(ctk.CTk):
    """
    Ventana unica de la aplicacion. Fullscreen sin bordes.
    Gestiona la transicion login <-> pantalla principal y el monitor USB.
    """

    def __init__(self):
        super().__init__()
        ctk.set_appearance_mode("dark")
        ctk.set_default_color_theme("blue")

        self.title("ICEBERG Manillas POS")
        self.configure(fg_color="#060b18")

        # Arranca maximizado con barra de titulo normal (visible en taskbar/Alt+Tab).
        # Al primer clic dentro de la interfaz se eliminan las decoraciones
        # via Windows API y queda en pantalla completa sin bordes.
        self.state("zoomed")

        # Estado interno
        self._main_frame: MainWindow | None = None
        self._login_frame: LoginScreen | None = None
        self._usb_popup: ctk.CTkFrame | None = None
        self._server_overlay: ctk.CTkFrame | None = None
        self._usb_was_connected: bool | None = None
        self._server_was_alive: bool | None = None
        self._in_main_phase = False
        self._kiosk_ready   = False
        self._is_fullscreen = False   # True cuando bordes quitados

        # USB bypass global (F5)
        self._usb_bypass = False
        self._usb_real = False

        # Indicador USB bypass — punto en esquina inferior derecha (siempre visible)
        self._lbl_usb_mode = ctk.CTkLabel(
            self, text="●", font=ctk.CTkFont(size=10), text_color="#4caf50",
        )
        self._lbl_usb_mode.place(relx=0.99, rely=0.99, anchor="se")
        self._lbl_usb_mode.lift()

        # F5 global — funciona en login y pantalla principal
        self.bind("<F5>", lambda e: self._toggle_usb_bypass())

        # Interceptar cierre de ventana para matar servicios
        self.protocol("WM_DELETE_WINDOW", self._on_close)

        self._show_login()

        # Primer clic → modo kiosk (sin bordes, una sola vez)
        self.bind("<Button-1>", self._on_first_interact, add="+")
        # F11 activa kiosk la primera vez; despues lo usa _on_first_interact
        self.bind("<F11>", self._on_first_interact)

        # Activar kiosk automáticamente al arrancar sin esperar clic
        self.after(1500, self._on_first_interact)

    def _on_first_interact(self, event=None):
        """Al primer clic/F11 entra en modo kiosk por primera vez."""
        if self._kiosk_ready:
            return
        self._kiosk_ready = True
        self.unbind("<Button-1>")
        # Despues de entrar en kiosk, F11 pasa a ser toggle
        self.after(200, lambda: self.bind("<F11>", self._toggle_fullscreen))
        self._enter_kiosk()

    def _toggle_fullscreen(self, event=None):
        """F11: alterna entre pantalla completa y ventana maximizada con bordes."""
        if self._is_fullscreen:
            self._exit_kiosk()
        else:
            self._enter_kiosk()

    def _enter_kiosk(self):
        """Pantalla completa: normaliza la ventana primero y luego aplica fullscreen.

        En el primer arranque el WM no ha confirmado aun el estado 'zoomed',
        por lo que aplicar fullscreen directamente produce un tamanio incorrecto.
        Solución: reset a estado conocido + breve delay antes de activar fullscreen.
        (Equivalente a presionar F11 dos veces seguidas de forma manual.)
        """
        # Marcar inmediatamente para que _toggle_fullscreen no re-entre durante el delay
        self._is_fullscreen = True
        self.attributes("-fullscreen", False)
        self.state("zoomed")
        self.update_idletasks()
        self.after(120, lambda: self.attributes("-fullscreen", True))

    def _exit_kiosk(self):
        """Sale de pantalla completa y vuelve a ventana maximizada con bordes."""
        self.attributes("-fullscreen", False)
        self.state("zoomed")
        self._is_fullscreen = False


    # ==================================================================
    # USB bypass (F5)
    # ==================================================================

    def _toggle_usb_bypass(self):
        """F5: alterna si el USB es obligatorio."""
        self._usb_bypass = not self._usb_bypass
        if self._usb_bypass:
            self._lbl_usb_mode.configure(text_color="#f44336")
            # Forzar estado OK en login
            if self._login_frame and self._login_frame.winfo_exists():
                self._login_frame.set_usb_bypass(True)
            # Ocultar popup USB en pantalla principal
            if self._in_main_phase and self._usb_popup is not None:
                self._hide_usb_popup()
        else:
            self._lbl_usb_mode.configure(text_color="#4caf50")
            if self._login_frame and self._login_frame.winfo_exists():
                self._login_frame.set_usb_bypass(False)
            # Restaurar alerta si USB real esta desconectado
            if self._in_main_phase and not self._usb_real:
                self._show_usb_popup()
        self._lbl_usb_mode.lift()

    # ==================================================================
    # Fase login
    # ==================================================================

    def _show_login(self):
        """Muestra el panel de login centrado en la ventana."""
        self._in_main_phase = False

        # Destruir contenido principal si existe
        if self._main_frame:
            self._main_frame.place_forget()
            self._main_frame.destroy()
            self._main_frame = None

        self._login_frame = LoginScreen(self, on_success=self._on_login_success,
                                        on_close=self._on_close)
        self._login_frame.place(relx=0.5, rely=0.5, anchor="center")

        # Primer chequeo con retardo (ventana debe estar lista)
        self.after(600, self._usb_login_poll)
        self.after(800, self._server_login_poll)

    def _usb_login_poll(self):
        """Verifica el estado USB cada 3 segundos mientras se muestra el login."""
        if self._login_frame is None or not self._login_frame.winfo_exists():
            return  # ya no estamos en login

        def _check():
            st = api.get_agent_status()
            connected = st.get("programmer_connected", False)
            try:
                self.after(0, lambda: self._apply_usb_login(connected))
            except Exception:
                pass

        threading.Thread(target=_check, daemon=True).start()

    def _apply_usb_login(self, connected: bool):
        if self._login_frame is None or not self._login_frame.winfo_exists():
            return
        self._usb_real = connected
        self._login_frame.set_usb_status(connected)
        if self._login_frame is not None:
            self.after(3000, self._usb_login_poll)

    # ------------------------------------------------------------------
    # Server status polling durante login
    # ------------------------------------------------------------------

    def _server_login_poll(self):
        """Verifica si el servidor esta activo cada 4 segundos durante login."""
        if self._login_frame is None or not self._login_frame.winfo_exists():
            return

        def _check():
            alive = api.check_server_alive()
            try:
                self.after(0, lambda: self._apply_server_login(alive))
            except Exception:
                pass

        threading.Thread(target=_check, daemon=True).start()

    def _apply_server_login(self, alive: bool):
        if self._login_frame is None or not self._login_frame.winfo_exists():
            return
        self._login_frame.set_server_status(alive)
        if self._login_frame is not None:
            self.after(4000, self._server_login_poll)

    # ==================================================================
    # Transicion login -> pantalla principal
    # ==================================================================

    def _on_login_success(self, user: dict):
        """Callback invocado por LoginScreen cuando la autenticacion es exitosa."""
        if self._login_frame:
            self._login_frame.place_forget()
            self._login_frame.destroy()
            self._login_frame = None

        session.set_session(
            user=user,
            permissions=user.get("permissions", []),
            venues=user.get("venues", []),
            venue_id=user.get("venue_id", ""),
            offline=user.get("offline", False),
        )

        self._show_main(user)

    # ==================================================================
    # Fase principal
    # ==================================================================

    def _show_main(self, user: dict):
        """Construye y muestra la pantalla principal."""
        self._in_main_phase = True

        self._main_frame = MainWindow(self, user=user, on_logout=self._on_logout)
        self._main_frame.place(x=0, y=0, relwidth=1, relheight=1)
        self._lbl_usb_mode.lift()

        # Reloj
        self._tick_clock()

        # Polling estado del servidor (indicador en header)
        self._server_main_poll()

        # Monitor USB: asume conectado al entrar (verificado en login)
        self._usb_was_connected = True
        self.after(5000, self._usb_main_poll)

    def _tick_clock(self):
        """Actualiza el reloj del header cada segundo."""
        if not self._in_main_phase or self._main_frame is None:
            return
        try:
            now = datetime.now()
            self._main_frame.set_clock(now.strftime("%H:%M:%S"), now.strftime("%d/%m/%Y"))
        except Exception:
            pass
        self.after(1000, self._tick_clock)

    # ==================================================================
    # Monitor USB durante sesion activa
    # ==================================================================

    def _server_main_poll(self):
        """Chequea estado del servidor cada 5s en la pantalla principal."""
        if not self._in_main_phase:
            return

        def _check():
            alive = api.check_server_alive()
            try:
                self.after(0, lambda: self._apply_server_main(alive))
            except Exception:
                pass

        threading.Thread(target=_check, daemon=True).start()

    def _apply_server_main(self, alive: bool):
        if not self._in_main_phase or self._main_frame is None:
            return
        try:
            self._main_frame.set_server_status(alive)
        except Exception:
            pass

        prev = self._server_was_alive
        self._server_was_alive = alive

        if not alive and self._server_overlay is None:
            self._show_server_overlay()
        elif alive and self._server_overlay is not None:
            self._hide_server_overlay()

        self.after(5000, self._server_main_poll)

    def _usb_main_poll(self):
        """Chequea estado USB cada 5s mientras esta en la pantalla principal."""
        if not self._in_main_phase:
            return

        def _check():
            st = api.get_agent_status()
            connected = st.get("programmer_connected", False)
            try:
                self.after(0, lambda: self._on_usb_main_status(connected))
            except Exception:
                pass

        threading.Thread(target=_check, daemon=True).start()
        self.after(5000, self._usb_main_poll)

    def _on_usb_main_status(self, connected: bool):
        if not self._in_main_phase:
            return
        prev = self._usb_was_connected
        self._usb_was_connected = connected
        self._usb_real = connected

        if self._usb_bypass:
            # Bypass activo: nunca mostrar popup
            if self._usb_popup is not None:
                self._hide_usb_popup()
            return

        if prev is True and not connected:
            # Recien se desconecto
            self._show_usb_popup()
        elif connected and self._usb_popup is not None:
            # Se reconecto -> cerrar alerta automaticamente
            self._hide_usb_popup()

    def _show_usb_popup(self):
        """Alerta grande top-center cuando el controlador USB se desconecta."""
        if self._usb_popup is not None:
            return  # ya visible

        popup = ctk.CTkFrame(
            self,
            fg_color="#5a0000",
            corner_radius=14,
            border_width=2,
            border_color="#ff1744",
        )
        # Centrado horizontalmente, pegado al top
        popup.place(relx=0.5, y=20, anchor="n")

        ctk.CTkLabel(
            popup,
            text="⚠   CONTROLADOR USB DESCONECTADO   ⚠",
            font=ctk.CTkFont(family="Segoe UI", size=24, weight="bold"),
            text_color="#ffffff",
        ).pack(padx=56, pady=(24, 6))

        ctk.CTkLabel(
            popup,
            text="El programador de pulseras se ha desconectado del equipo.\n"
                 "Vuelve a conectar el dispositivo USB para seguir operando.",
            font=ctk.CTkFont(size=14),
            text_color="#ffaaaa",
            justify="center",
        ).pack(padx=56, pady=(0, 24))

        self._usb_popup = popup
        popup.lift()  # siempre encima

    def _hide_usb_popup(self):
        if self._usb_popup:
            self._usb_popup.place_forget()
            self._usb_popup.destroy()
            self._usb_popup = None

    # ==================================================================
    # Overlay: servidor desconectado (bloquea toda interaccion)
    # ==================================================================

    def _show_server_overlay(self):
        """Overlay fullscreen que bloquea la app cuando el servidor cae."""
        if self._server_overlay is not None:
            return

        overlay = ctk.CTkFrame(self, fg_color="#0a0008", corner_radius=0)
        overlay.place(relx=0, rely=0, relwidth=1, relheight=1)
        overlay.lift()

        # Interceptar clics para que no pasen a los widgets de abajo
        overlay.bind("<Button-1>", lambda e: "break")

        # Contenedor centrado
        box = ctk.CTkFrame(overlay, fg_color="#120010",
                           corner_radius=18, border_width=3,
                           border_color="#ff1744")
        box.place(relx=0.5, rely=0.5, anchor="center")

        ctk.CTkLabel(
            box, text="ERROR MASTER SERVER",
            font=ctk.CTkFont(family="Segoe UI", size=42, weight="bold"),
            text_color="#ff1744",
        ).pack(padx=80, pady=(48, 8))

        ctk.CTkLabel(
            box, text="Servidor desconectado",
            font=ctk.CTkFont(family="Segoe UI", size=20),
            text_color="#ff8a80",
        ).pack(pady=(0, 8))

        ctk.CTkLabel(
            box,
            text="No se puede operar sin conexion al servidor.\n"
                 "Verificar que el servidor este encendido y accesible.",
            font=ctk.CTkFont(size=14),
            text_color="#888888",
            justify="center",
        ).pack(pady=(0, 12))

        self._server_spinner = ctk.CTkLabel(
            box, text="Reconectando...",
            font=ctk.CTkFont(size=13),
            text_color="#555555",
        )
        self._server_spinner.pack(pady=(0, 48))

        self._server_overlay = overlay

    def _hide_server_overlay(self):
        if self._server_overlay is not None:
            self._server_overlay.place_forget()
            self._server_overlay.destroy()
            self._server_overlay = None

    # ==================================================================
    # Cierre de la aplicacion
    # ==================================================================

    def _on_close(self):
        """Intercepta el cierre de ventana (X, Alt+F4) para matar servicios."""
        self._in_main_phase = False
        _kill_services()
        self.destroy()

    # ==================================================================
    # Logout
    # ==================================================================

    def _on_logout(self):
        """Llamado por MainWindow cuando el cajero cierra sesion."""
        self._in_main_phase = False
        self._hide_usb_popup()

        # Obtener nombre del cajero antes de limpiar la sesion
        cashier_name = ""
        if self._main_frame:
            cashier_name = self._main_frame._user.get("display_name", "")

        # Imprimir reporte de cierre de ventas antes de limpiar la sesion
        def _print_cierre():
            try:
                data = api.get_daily_report()
                if isinstance(data, dict) and not data.get("error"):
                    printing.ReporteCierre.desde_reporte(data, cashier_name=cashier_name)
            except Exception:
                pass

        threading.Thread(target=_print_cierre, daemon=True).start()

        session.clear()
        self._show_login()


def _kill_services():
    """Mata los procesos del agente (5555) y servidor (8000) y sus terminales."""
    import subprocess
    CW = 0x08000000  # CREATE_NO_WINDOW

    # 1) Matar procesos que escuchan en los puertos del agente y servidor
    for port in (5555, 8000):
        try:
            r = subprocess.run(
                f'netstat -aon | findstr ":{port} "',
                capture_output=True, text=True, shell=True,
                creationflags=CW,
            )
            killed = set()
            for line in r.stdout.splitlines():
                parts = line.split()
                if len(parts) >= 5:
                    pid = parts[-1].strip()
                    if pid.isdigit() and pid != "0" and pid not in killed:
                        subprocess.run(
                            f'taskkill /F /PID {pid}',
                            shell=True, capture_output=True,
                            creationflags=CW,
                        )
                        killed.add(pid)
        except Exception:
            pass

    # 2) Cerrar las 3 ventanas CMD abiertas por iceberg.bat
    for title in ("ICEBERG - Agent", "ICEBERG - Servidor", "ICEBERG - App"):
        try:
            subprocess.run(
                f'taskkill /F /FI "WINDOWTITLE eq {title}"',
                shell=True, capture_output=True,
                creationflags=CW,
            )
        except Exception:
            pass


def main():
    atexit.register(_kill_services)
    sync_worker.start()
    app = AppWindow()
    app.mainloop()
    _kill_services()


if __name__ == "__main__":
    main()
