"""
Popup flotante de venta (estilo ICEBERG).
Aparece al seleccionar un combo. Pide nombre y referencia del jugador.
Fase 1: formulario minimalista (nombre obligatorio)
Fase 2: animacion "acerque la manilla al lector" (pulso IR)
Fase 3: resultado con checkmark y folio

Diseño: popup flotante always-on-top, sin bordes del OS (estilo ICEBERG).
"""
import math
import threading
import tkinter as tk
import customtkinter as ctk
from typing import Callable, Optional
import api
import wristband_tracker
import offline_queue
from ui.kb_shortcuts import bind_entry

# ---------------------------------------------------------------------------
# Colores ICEBERG
# ---------------------------------------------------------------------------
_BG_CARD     = "#0f1228"
_OK_GREEN    = "#4caf50"
_ERR_RED     = "#f44336"
_ORANGE      = "#ff9800"
_CYAN        = "#4fc3f7"
_TEXT_DIM    = "#666677"
_TEXT_MID    = "#9999aa"
_SEPARATOR   = "#1a2440"
_BTN_CANCEL  = "transparent"
_BTN_CANCEL_HV = "#1a2440"
_BTN_RETRY   = "#e65100"
_BTN_RETRY_HV = "#bf360c"

_NETWORK_ERRORS = (
    "No se puede conectar",
    "El servidor no responde",
    "timeout de red",
)

STATE_IDLE    = "idle"
STATE_WAITING = "waiting"
STATE_OK      = "ok"
STATE_TIMEOUT = "timeout"
STATE_ERROR   = "error"


def _is_network_error(msg: str) -> bool:
    return any(s in msg for s in _NETWORK_ERRORS)


def _hex_to_rgb(h: str):
    h = h.lstrip("#")
    return int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)


def _rgb_to_hex(r, g, b):
    return f"#{int(r):02x}{int(g):02x}{int(b):02x}"


def _lerp_color(c1: str, c2: str, t: float) -> str:
    r1, g1, b1 = _hex_to_rgb(c1)
    r2, g2, b2 = _hex_to_rgb(c2)
    return _rgb_to_hex(
        r1 + (r2 - r1) * t,
        g1 + (g2 - g1) * t,
        b1 + (b2 - b1) * t,
    )


class SaleDialog(ctk.CTkToplevel):
    """
    Popup flotante ICEBERG para venta de pulsera.
    on_sale_complete(session_data) se llama cuando la venta finaliza.
    """

    def __init__(
        self,
        parent,
        package: dict,
        cashier: str = "caja1",
        on_sale_complete: Optional[Callable] = None,
    ):
        super().__init__(parent)
        self._package = package
        self._cashier = cashier
        self.on_sale_complete = on_sale_complete

        self._state = STATE_IDLE
        self._result: Optional[dict] = None
        self._session_id: Optional[str] = None
        self._customer_name = ""
        self._customer_ref = ""
        self._pulse_step = 0
        self._animating = False

        # Color acento del paquete
        self._accent = package.get("color1", "#1565c0")
        self._accent_dim = _lerp_color(self._accent, "#000000", 0.55)

        # Ventana sin bordes del OS, siempre encima
        self.overrideredirect(True)
        self.attributes("-topmost", True)
        self.configure(fg_color=_BG_CARD)

        # Tamaño y posicion centrada sobre el padre
        self._win_w = 440
        self._win_h = 480
        self.geometry(f"{self._win_w}x{self._win_h}")
        self._center_on_parent(parent)

        # Grab modal
        self.grab_set()
        self.focus_force()

        # Card principal
        self._card = ctk.CTkFrame(
            self,
            fg_color=_BG_CARD,
            corner_radius=0,
            border_width=2,
            border_color=self._accent_dim,
        )
        self._card.pack(fill="both", expand=True)

        # Permitir mover la ventana arrastrando
        self._drag_data = {"x": 0, "y": 0}
        self._card.bind("<Button-1>", self._start_drag)
        self._card.bind("<B1-Motion>", self._do_drag)

        self._build_form()
        self.bind("<Escape>", lambda e: self._close())
        self.bind("<Return>", lambda e: self._on_sell())

    # ------------------------------------------------------------------
    # Posicion y arrastre
    # ------------------------------------------------------------------

    def _center_on_parent(self, parent):
        self.update_idletasks()
        px = parent.winfo_rootx() + parent.winfo_width() // 2
        py = parent.winfo_rooty() + parent.winfo_height() // 2
        x = px - self._win_w // 2
        y = py - self._win_h // 2
        self.geometry(f"{self._win_w}x{self._win_h}+{x}+{y}")

    def _start_drag(self, event):
        self._drag_data["x"] = event.x
        self._drag_data["y"] = event.y

    def _do_drag(self, event):
        x = self.winfo_x() + (event.x - self._drag_data["x"])
        y = self.winfo_y() + (event.y - self._drag_data["y"])
        self.geometry(f"+{x}+{y}")

    # ------------------------------------------------------------------
    # Fase 1: formulario
    # ------------------------------------------------------------------

    def _build_form(self):
        card = self._card
        pkg = self._package
        mins = pkg["minutes"]
        if mins >= 60 and mins % 60:
            time_str = f"{mins // 60}h {mins % 60}min"
        elif mins >= 60:
            time_str = f"{mins // 60}h"
        else:
            time_str = f"{mins} min"

        # Barra acento superior
        ctk.CTkFrame(
            card, height=4, fg_color=self._accent, corner_radius=0,
        ).pack(fill="x", side="top")

        # Icono
        ctk.CTkLabel(
            card, text="\U0001F4F6",
            font=ctk.CTkFont(size=36),
        ).pack(pady=(18, 0))

        # Nombre del paquete
        ctk.CTkLabel(
            card, text=pkg["name"],
            font=ctk.CTkFont(size=20, weight="bold"),
            text_color=self._accent,
        ).pack(pady=(4, 0))

        # Tiempo y precio
        ctk.CTkLabel(
            card,
            text=f"{time_str}   \u00b7   ${pkg['price']:.2f}",
            font=ctk.CTkFont(size=13),
            text_color=_TEXT_DIM,
        ).pack(pady=(0, 12))

        # Separador
        ctk.CTkFrame(card, height=1, fg_color=_SEPARATOR, corner_radius=0).pack(
            fill="x", padx=30
        )

        # Nombre del jugador (OBLIGATORIO)
        ctk.CTkLabel(
            card, text="Nombre del jugador:",
            font=ctk.CTkFont(size=12), text_color=_TEXT_MID,
        ).pack(anchor="w", padx=30, pady=(14, 2))

        self._entry_name = ctk.CTkEntry(
            card, placeholder_text="Nombre del jugador",
            font=ctk.CTkFont(size=13), height=36,
            fg_color="#1a2440", border_color="#2a3a5a",
        )
        self._entry_name.pack(fill="x", padx=30, pady=(0, 10))

        # Referencia
        ctk.CTkLabel(
            card, text="Referencia:",
            font=ctk.CTkFont(size=12), text_color=_TEXT_MID,
        ).pack(anchor="w", padx=30, pady=(0, 2))

        self._entry_ref = ctk.CTkEntry(
            card, placeholder_text="Codigo, ticket, etc. (opcional)",
            font=ctk.CTkFont(size=13), height=36,
            fg_color="#1a2440", border_color="#2a3a5a",
        )
        self._entry_ref.pack(fill="x", padx=30, pady=(0, 18))

        # Boton ACTIVAR con color acento
        self._btn_sell = ctk.CTkButton(
            card, text="ACTIVAR  \u2192  ACERCAR PULSERA",
            font=ctk.CTkFont(size=14, weight="bold"), height=50,
            corner_radius=10, fg_color=self._accent_dim,
            hover_color=_lerp_color(self._accent, "#000000", 0.35),
            command=self._on_sell,
        )
        self._btn_sell.pack(fill="x", padx=30, pady=(0, 6))

        # Estado / validacion
        self._lbl_status = ctk.CTkLabel(
            card, text="",
            font=ctk.CTkFont(size=13), wraplength=380, justify="center",
        )
        self._lbl_status.pack(pady=(0, 4))

        # Cancelar (ghost button)
        ctk.CTkButton(
            card, text="Cancelar",
            font=ctk.CTkFont(size=12), height=30,
            fg_color=_BTN_CANCEL, hover_color=_BTN_CANCEL_HV,
            text_color=_TEXT_DIM,
            command=self._close,
        ).pack(pady=(0, 14))

        self._entry_name.focus_set()
        bind_entry(self._entry_name)
        bind_entry(self._entry_ref)

    # ------------------------------------------------------------------
    # Fase 2: acerque la manilla (con animacion de pulso IR)
    # ------------------------------------------------------------------

    def _show_waiting(self):
        for w in self._card.winfo_children():
            w.destroy()

        accent = self._accent

        # Barra acento superior
        ctk.CTkFrame(
            self._card, height=4, fg_color=accent, corner_radius=0,
        ).pack(fill="x", side="top")

        # Canvas para anillo pulsante
        self._pulse_canvas = tk.Canvas(
            self._card, width=140, height=140,
            bg=_BG_CARD, highlightthickness=0, bd=0,
        )
        self._pulse_canvas.pack(pady=(30, 4))

        self._lbl_waiting = ctk.CTkLabel(
            self._card,
            text="Acerque la manilla\nal lector",
            font=ctk.CTkFont(size=20, weight="bold"),
            text_color=accent, justify="center",
        )
        self._lbl_waiting.pack(pady=(0, 6))

        self._lbl_status = ctk.CTkLabel(
            self._card, text="Esperando dispositivo...",
            font=ctk.CTkFont(size=11), text_color=_TEXT_DIM,
            wraplength=360, justify="center",
        )
        self._lbl_status.pack(pady=(0, 8))

        # Retry (oculto hasta timeout)
        self._btn_retry = ctk.CTkButton(
            self._card, text="Reintentar (acercar pulsera nuevamente)",
            height=40, corner_radius=10,
            fg_color=_BTN_RETRY, hover_color=_BTN_RETRY_HV,
            font=ctk.CTkFont(size=13, weight="bold"),
            command=self._on_retry,
        )

        # Cancelar
        self._btn_cancel_wait = ctk.CTkButton(
            self._card, text="Cancelar",
            font=ctk.CTkFont(size=12), height=30,
            fg_color=_BTN_CANCEL, hover_color=_BTN_CANCEL_HV,
            text_color=_TEXT_DIM,
            command=self._close,
        )
        self._btn_cancel_wait.pack(pady=(4, 22))

        # Iniciar animacion
        self._pulse_step = 0
        self._animating = True
        self._animate_pulse()

    def _animate_pulse(self):
        if not self._animating:
            return
        try:
            cv = self._pulse_canvas
            cv.delete("all")
            cx, cy = 70, 70
            accent = self._accent

            # Anillo exterior pulsante
            phase = self._pulse_step / 40.0 * 2 * math.pi
            r_outer = 42 + 10 * math.sin(phase)
            alpha = 0.4 + 0.3 * math.sin(phase)
            outer_color = _lerp_color(accent, _BG_CARD, 1.0 - alpha)
            cv.create_oval(
                cx - r_outer, cy - r_outer, cx + r_outer, cy + r_outer,
                outline=outer_color, width=3, fill="",
            )

            # Segundo anillo pulsante (desfasado)
            r_mid = 32 + 6 * math.sin(phase + 1.5)
            mid_alpha = 0.3 + 0.2 * math.sin(phase + 1.5)
            mid_color = _lerp_color(accent, _BG_CARD, 1.0 - mid_alpha)
            cv.create_oval(
                cx - r_mid, cy - r_mid, cx + r_mid, cy + r_mid,
                outline=mid_color, width=2, fill="",
            )

            # Anillo interior
            r_inner = 22 + 4 * math.sin(phase + 3.0)
            cv.create_oval(
                cx - r_inner, cy - r_inner, cx + r_inner, cy + r_inner,
                outline=accent, width=2, fill="",
            )

            # Punto central
            dot_r = 5 + 1 * math.sin(phase)
            cv.create_oval(
                cx - dot_r, cy - dot_r, cx + dot_r, cy + dot_r,
                fill=accent, outline="",
            )

            self._pulse_step += 1
            self.after(33, self._animate_pulse)  # ~30fps
        except Exception:
            pass

    # ------------------------------------------------------------------
    # Fase 3: resultado (estilo ICEBERG con checkmark)
    # ------------------------------------------------------------------

    def _show_result(self, r: dict):
        self._animating = False
        for w in self._card.winfo_children():
            w.destroy()

        if r.get("_offline_queued"):
            color = _ORANGE
            title = "Pulsera programada"
            subtitle = "Guardada offline \u2014 se sincronizara automaticamente"
            icon = "\u26A0"
            bar_color = _ORANGE
        else:
            color = _OK_GREEN
            title = "Pulsera activada!"
            subtitle = ""
            icon = "\u2713"
            bar_color = _OK_GREEN

        # Barra de resultado
        ctk.CTkFrame(
            self._card, height=4, fg_color=bar_color, corner_radius=0,
        ).pack(fill="x", side="top")

        # Icono grande (checkmark o warning)
        ctk.CTkLabel(
            self._card, text=icon,
            font=ctk.CTkFont(size=52, weight="bold"),
            text_color=color,
        ).pack(pady=(36, 6))

        # Titulo
        ctk.CTkLabel(
            self._card, text=title,
            font=ctk.CTkFont(size=20, weight="bold"),
            text_color=color,
        ).pack(pady=(0, 6))

        # Folio
        folio = r.get("folio_number", "")
        if folio:
            ctk.CTkLabel(
                self._card, text=f"Folio: {folio}",
                font=ctk.CTkFont(size=14),
                text_color="#ffffff",
            ).pack(pady=(0, 4))

        # Info del paquete
        pkg_name = r.get("package_name", "")
        amount = r.get("amount", 0)
        if pkg_name:
            ctk.CTkLabel(
                self._card,
                text=f"{pkg_name}   \u00b7   ${amount:.2f}",
                font=ctk.CTkFont(size=13),
                text_color=_TEXT_MID,
            ).pack(pady=(0, 4))

        # Nombre del cliente
        if self._customer_name:
            ctk.CTkLabel(
                self._card,
                text=f"Cliente: {self._customer_name}",
                font=ctk.CTkFont(size=12),
                text_color=_TEXT_DIM,
            ).pack(pady=(0, 2))

        # Subtitle (offline)
        if subtitle:
            ctk.CTkLabel(
                self._card, text=subtitle,
                font=ctk.CTkFont(size=11),
                text_color=_TEXT_DIM,
            ).pack(pady=(4, 0))

        # Spacer
        ctk.CTkLabel(self._card, text="").pack(pady=(0, 28))

        # Borde cambia a color resultado
        self._card.configure(border_color=color)

    # ------------------------------------------------------------------
    # Flujo de venta
    # ------------------------------------------------------------------

    def _on_sell(self):
        if self._state == STATE_WAITING:
            return

        # Validar nombre obligatorio
        name = self._entry_name.get().strip()
        if not name:
            self._lbl_status.configure(
                text="El nombre del jugador es obligatorio.", text_color=_ERR_RED,
            )
            self._entry_name.configure(border_color=_ERR_RED)
            self._entry_name.focus_set()
            return

        # Limpiar error visual
        self._entry_name.configure(border_color="#2a3a5a")

        self._customer_name = name
        self._customer_ref = self._entry_ref.get().strip()

        self._result = None
        self._session_id = None
        self._state = STATE_WAITING

        self._show_waiting()

        t = threading.Thread(target=self._do_sell, daemon=True)
        t.start()
        self.after(200, self._poll)

    def _do_sell(self):
        from config import TEST_MODE
        pkg = self._package

        if TEST_MODE:
            self._do_sell_via_agent(green=1, blue=1, red=1)
            return

        # Parametros extra para combos custom (RGB + agent_id)
        extra = {}
        if pkg.get("custom"):
            extra["agent_id"] = api.get_agent_id()
            extra["red_minutes"] = pkg.get("minutes_red", 0)
            extra["green_minutes"] = pkg.get("minutes_green", 0)
            extra["blue_minutes"] = pkg.get("minutes_blue", 0)

        result = api.sell(
            package_id=pkg["id"],
            payment_method="cash",
            amount_received=float(pkg["price"]),
            cashier=self._cashier,
            **extra,
        )

        if result.get("error") and _is_network_error(result["error"]):
            self._result = self._do_sell_offline(result["error"])
            return

        self._result = result

    def _do_sell_offline(self, server_error: str) -> dict:
        import uuid
        import session as _session

        pkg = self._package
        mins = pkg.get("minutes", 0)

        # Usar RGB del combo custom si existen; fallback a esquema por color
        if pkg.get("custom"):
            r = max(pkg.get("minutes_red", 0), 1)
            g = max(pkg.get("minutes_green", 0), 1)
            b = max(pkg.get("minutes_blue", 0), 1)
        else:
            color = pkg.get("color", "blue")
            if color == "red":
                r, g, b = mins, 1, 1
            elif color == "green":
                r, g, b = 1, mins, 1
            elif color == "all":
                r, g, b = mins, mins, mins
            else:  # blue (default)
                r, g, b = 1, 1, mins
            # Clamp min 1 por protocolo IR
            r = max(r, 1)
            g = max(g, 1)
            b = max(b, 1)

        agent_result = api.program_via_agent(
            green_minutes=g, blue_minutes=b, red_minutes=r,
        )

        if agent_result.get("status") == "ok":
            session_id = str(uuid.uuid4())[:8]
            programmed_at = agent_result.get("programmed_at", "")
            venue_id = _session.get_venue_id() or api.get_venue_id()

            offline_queue.enqueue(
                session_id=session_id,
                package_id=pkg.get("id", ""),
                package_name=pkg.get("name", ""),
                payment_method="cash",
                amount=float(pkg.get("price", 0)),
                cashier=self._cashier,
                minutes=mins,
                green_minutes=g, blue_minutes=b, red_minutes=r,
                programmed_at=programmed_at,
                venue_id=venue_id,
            )
            return {
                "status": "ok",
                "session_id": session_id,
                "package_name": pkg.get("name", ""),
                "minutes": mins,
                "amount": float(pkg.get("price", 0)),
                "programmed_at": programmed_at,
                "_offline_queued": True,
            }
        elif agent_result.get("status") == "timeout":
            return {"status": "timeout", "session_id": None}
        else:
            return {"error": server_error}

    def _do_sell_via_agent(self, green: int, blue: int, red: int):
        import uuid
        pkg = self._package
        result = api.program_via_agent(
            green_minutes=green, blue_minutes=blue, red_minutes=red,
        )
        if result.get("status") == "ok":
            self._result = {
                "status": "ok",
                "session_id": str(uuid.uuid4()),
                "package_name": pkg["name"],
                "minutes": pkg["minutes"],
                "amount": pkg["price"],
                "programmed_at": result.get("programmed_at", ""),
            }
        elif result.get("status") == "timeout":
            self._result = {"status": "timeout", "session_id": None}
        else:
            self._result = result

    # ------------------------------------------------------------------
    # Polling
    # ------------------------------------------------------------------

    def _poll(self):
        if self._result is None:
            self.after(300, self._poll)
            return

        r = self._result
        self._result = None

        if r.get("error"):
            self._animating = False
            self._session_id = None
            self._state = STATE_ERROR
            err_msg = r["error"]
            if "no asignado" in err_msg.lower():
                self._lbl_waiting.configure(
                    text="Combo sin asignar", text_color=_ORANGE,
                )
                self._lbl_status.configure(
                    text="Este combo necesita ser configurado\ndesde el Operator Panel.",
                    text_color=_ORANGE,
                )
            else:
                self._lbl_waiting.configure(
                    text="Error de conexion", text_color=_ERR_RED,
                )
                self._lbl_status.configure(
                    text=err_msg, text_color=_ERR_RED,
                )

        elif r.get("status") == "ok":
            self._session_id = r.get("session_id")
            self._state = STATE_OK

            wristband_tracker.add(
                name=self._customer_name,
                reference=self._customer_ref,
                session_id=self._session_id,
                package_id=self._package["id"],
                package_name=self._package["name"],
                real_play_minutes=r.get("minutes", self._package["minutes"]),
            )

            r["customer_name"] = self._customer_name

            import printing as _printing
            import threading as _threading
            _threading.Thread(
                target=_printing.print_after_sale, args=(r,), daemon=True
            ).start()

            self._show_result(r)

            if self.on_sale_complete:
                self.on_sale_complete(r)

            delay = 4000 if r.get("_offline_queued") else 2500
            self.after(delay, self._close)

        elif r.get("status") == "timeout":
            self._animating = False
            self._session_id = r.get("session_id")
            self._state = STATE_TIMEOUT
            self._lbl_waiting.configure(
                text="Pulsera no detectada", text_color=_ORANGE,
            )
            self._lbl_status.configure(
                text="Acerque la pulsera e intente de nuevo.",
                text_color=_ORANGE,
            )
            self._btn_retry.pack(fill="x", padx=30, pady=(0, 6))
            self._btn_cancel_wait.pack(pady=(0, 14))

        else:
            self._animating = False
            self._state = STATE_ERROR
            msg = r.get("message", "") or str(r)
            self._lbl_waiting.configure(
                text="Error", text_color=_ERR_RED,
            )
            self._lbl_status.configure(
                text=msg, text_color=_ERR_RED,
            )

    # ------------------------------------------------------------------
    # Retry
    # ------------------------------------------------------------------

    def _on_retry(self):
        self._btn_retry.pack_forget()
        self._lbl_waiting.configure(
            text="Acerque la manilla\nal lector",
            text_color=self._accent,
        )
        self._lbl_status.configure(text="Esperando dispositivo...", text_color=_TEXT_DIM)
        self._result = None
        self._state = STATE_WAITING
        self._animating = True
        self._pulse_step = 0
        self._animate_pulse()

        if not self._session_id:
            t = threading.Thread(target=self._do_sell, daemon=True)
        else:
            t = threading.Thread(target=self._do_retry, daemon=True)
        t.start()
        self.after(200, self._poll)

    def _do_retry(self):
        self._result = api.retry_sell(self._session_id)

    # ------------------------------------------------------------------

    def _close(self):
        self._animating = False
        self._state = STATE_IDLE
        try:
            self.grab_release()
        except Exception:
            pass
        self.destroy()
