# =============================================================================
#  installer/src/install.py — AR Electronics J6412 installer
# =============================================================================
#
#  Tkinter GUI installer that:
#    1. Validates the bundled serial number
#    2. Installs AR-ECDIS and AR-Autopilot Display to Program Files
#    3. Activates the license online
#    4. Configures Windows autostart, shortcuts, and firewall rules
#
#  Usage:
#    python install.py          — interactive GUI mode
#    python install.py --silent — headless mode (for testing / scripted deploy)
#
#  This file lives in the root of the USB pendrive alongside serial.key and
#  the packages/ directory.  Run as Administrator for full functionality.
# =============================================================================

from __future__ import annotations

import argparse
import shutil
import subprocess
import sys
import threading
import tkinter as tk
from pathlib import Path
from tkinter import messagebox, ttk

# ── Resolve installer root (directory containing this script) ─────────────────
INSTALLER_DIR = Path(__file__).parent
PACKAGES_DIR  = INSTALLER_DIR / "packages"

APP_VERSION = "0.4.0"

# ── Late imports from sibling modules ─────────────────────────────────────────
sys.path.insert(0, str(INSTALLER_DIR))
from license   import activate_online, read_serial, ActivationError   # noqa: E402
from sysconfig import (                                                 # noqa: E402
    ECDIS_DIR, AUTOPILOT_DIR,
    ensure_install_dirs, configure_autostart,
    create_shortcuts, add_firewall_rules, list_com_ports,
    is_admin, require_admin,
)

# ---------------------------------------------------------------------------
#  Installation steps
# ---------------------------------------------------------------------------

STEPS = [
    ("Verificar serial",       "_step_verify_serial"),
    ("Crear directorios",      "_step_create_dirs"),
    ("Instalar AR-ECDIS",      "_step_install_ecdis"),
    ("Instalar AR-Autopilot",  "_step_install_autopilot"),
    ("Activar licencia",       "_step_activate_license"),
    ("Configurar inicio",      "_step_configure_autostart"),
    ("Accesos directos",       "_step_create_shortcuts"),
    ("Reglas de firewall",     "_step_firewall"),
    ("Finalizar",              "_step_finalize"),
]


class Installer:
    """Core installation logic — UI-independent."""

    def __init__(self, log_fn=print):
        self._log = log_fn
        self.serial: str | None = None

    # ── Step implementations ─────────────────────────────────────────────────

    def _step_verify_serial(self):
        self._log("Leyendo número de serie…")
        self.serial = read_serial(INSTALLER_DIR)
        self._log(f"Serial: {self.serial}")

    def _step_create_dirs(self):
        self._log("Creando directorios en Program Files…")
        ensure_install_dirs()

    def _step_install_ecdis(self):
        src = PACKAGES_DIR / "AR-ECDIS"
        if not src.exists():
            self._log("Paquete AR-ECDIS no encontrado — omitiendo.")
            return
        self._log(f"Copiando AR-ECDIS → {ECDIS_DIR}")
        if ECDIS_DIR.exists():
            shutil.rmtree(ECDIS_DIR)
        shutil.copytree(src, ECDIS_DIR)
        self._log("AR-ECDIS instalado correctamente.")

    def _step_install_autopilot(self):
        src = PACKAGES_DIR / "AR-Autopilot"
        if not src.exists():
            self._log("Paquete AR-Autopilot no encontrado — omitiendo.")
            return
        self._log(f"Copiando AR-Autopilot → {AUTOPILOT_DIR}")
        if AUTOPILOT_DIR.exists():
            shutil.rmtree(AUTOPILOT_DIR)
        shutil.copytree(src, AUTOPILOT_DIR)
        self._log("AR-Autopilot instalado correctamente.")

    def _step_activate_license(self):
        if self.serial is None:
            raise RuntimeError("Serial no disponible para activación.")
        self._log(f"Activando licencia en servidor AR Electronics…")
        result = activate_online(self.serial, app_version=APP_VERSION)
        self._log(
            f"Licencia activada — ID: {result.activation_id[:8]}…\n"
            f"  Slot: {result.vessel_slot}  |  {result.licensed_to}"
        )

    def _step_configure_autostart(self):
        self._log("Configurando inicio automático con Windows…")
        configure_autostart()

    def _step_create_shortcuts(self):
        self._log("Creando accesos directos…")
        create_shortcuts()

    def _step_firewall(self):
        if not is_admin():
            self._log("Sin privilegios de administrador — omitiendo reglas de firewall.")
            return
        self._log("Añadiendo reglas de firewall…")
        add_firewall_rules()

    def _step_finalize(self):
        ports = list_com_ports()
        if ports:
            self._log(f"Puertos COM detectados: {', '.join(ports)}")
            self._log(
                "Conecte el concentrador y configure los puertos en\n"
                "AR-Autopilot → Ajustes → Puertos COM."
            )
        else:
            self._log("No se detectaron puertos COM — conecte el concentrador USB.")
        self._log("Instalación completada con éxito.")

    # ── Public runner ────────────────────────────────────────────────────────

    def run_all(self, progress_cb=None):
        """
        Execute all steps sequentially.

        :param progress_cb: optional callable(step_index, step_name, success, error)
        """
        for idx, (name, method_name) in enumerate(STEPS):
            method = getattr(self, method_name)
            try:
                method()
                if progress_cb:
                    progress_cb(idx, name, True, None)
            except ActivationError as exc:
                if progress_cb:
                    progress_cb(idx, name, False, exc)
                raise
            except Exception as exc:
                if progress_cb:
                    progress_cb(idx, name, False, exc)
                raise


# ---------------------------------------------------------------------------
#  Tkinter GUI
# ---------------------------------------------------------------------------

BRAND_NAVY  = "#0D1B2A"
BRAND_BLUE  = "#2563EB"
BRAND_GLOW  = "#60B8FF"
BRAND_TEXT  = "#E2E8F0"
BRAND_MUTED = "#8899AA"
BRAND_GREEN = "#22C55E"
BRAND_RED   = "#EF4444"


class InstallerWindow:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("AR Electronics — Instalador J6412")
        self.root.configure(bg=BRAND_NAVY)
        self.root.resizable(False, False)

        # Centre on screen
        w, h = 560, 520
        sw = self.root.winfo_screenwidth()
        sh = self.root.winfo_screenheight()
        self.root.geometry(f"{w}x{h}+{(sw-w)//2}+{(sh-h)//2}")

        self._build_ui()
        self._installer = Installer(log_fn=self._append_log)

    # ── UI construction ──────────────────────────────────────────────────────

    def _build_ui(self):
        # Header
        hdr = tk.Frame(self.root, bg=BRAND_NAVY)
        hdr.pack(fill="x", padx=0, pady=0)

        tk.Label(
            hdr,
            text="AR Electronics",
            font=("Segoe UI", 18, "bold"),
            fg=BRAND_GLOW,
            bg=BRAND_NAVY,
        ).pack(pady=(20, 0))

        tk.Label(
            hdr,
            text="Instalador para J6412 Mini PC",
            font=("Segoe UI", 11),
            fg=BRAND_MUTED,
            bg=BRAND_NAVY,
        ).pack(pady=(0, 12))

        sep = tk.Frame(self.root, height=1, bg=BRAND_BLUE)
        sep.pack(fill="x", padx=20)

        # Steps frame
        self._step_vars: list[tk.StringVar] = []
        steps_frame = tk.Frame(self.root, bg=BRAND_NAVY)
        steps_frame.pack(fill="x", padx=30, pady=14)

        for _, (name, _) in enumerate(STEPS):
            var = tk.StringVar(value=f"  ○  {name}")
            lbl = tk.Label(
                steps_frame,
                textvariable=var,
                font=("Consolas", 10),
                fg=BRAND_MUTED,
                bg=BRAND_NAVY,
                anchor="w",
            )
            lbl.pack(fill="x", pady=1)
            self._step_vars.append(var)

        self._step_labels = steps_frame.winfo_children()

        # Progress bar
        pb_frame = tk.Frame(self.root, bg=BRAND_NAVY)
        pb_frame.pack(fill="x", padx=30, pady=(0, 8))

        style = ttk.Style()
        style.theme_use("clam")
        style.configure(
            "AR.Horizontal.TProgressbar",
            troughcolor=BRAND_NAVY,
            bordercolor=BRAND_BLUE,
            background=BRAND_GLOW,
            lightcolor=BRAND_GLOW,
            darkcolor=BRAND_BLUE,
        )
        self._progress = ttk.Progressbar(
            pb_frame,
            style="AR.Horizontal.TProgressbar",
            maximum=len(STEPS),
            length=500,
        )
        self._progress.pack(fill="x")

        # Log text
        log_frame = tk.Frame(self.root, bg="#0A1520")
        log_frame.pack(fill="both", expand=True, padx=20, pady=(0, 12))

        self._log_text = tk.Text(
            log_frame,
            height=7,
            font=("Consolas", 9),
            bg="#0A1520",
            fg=BRAND_MUTED,
            relief="flat",
            state="disabled",
            wrap="word",
        )
        self._log_text.pack(fill="both", expand=True, padx=8, pady=6)

        # Buttons
        btn_frame = tk.Frame(self.root, bg=BRAND_NAVY)
        btn_frame.pack(fill="x", padx=20, pady=(0, 20))

        self._install_btn = tk.Button(
            btn_frame,
            text="INSTALAR",
            font=("Segoe UI", 10, "bold"),
            bg=BRAND_BLUE,
            fg="white",
            activebackground=BRAND_GLOW,
            relief="flat",
            padx=24,
            pady=8,
            cursor="hand2",
            command=self._start_install,
        )
        self._install_btn.pack(side="left")

        self._cancel_btn = tk.Button(
            btn_frame,
            text="Cancelar",
            font=("Segoe UI", 10),
            bg=BRAND_NAVY,
            fg=BRAND_MUTED,
            activeforeground=BRAND_TEXT,
            relief="flat",
            padx=16,
            pady=8,
            cursor="hand2",
            command=self.root.destroy,
        )
        self._cancel_btn.pack(side="left", padx=(10, 0))

        self._status_lbl = tk.Label(
            btn_frame,
            text="",
            font=("Segoe UI", 9),
            fg=BRAND_MUTED,
            bg=BRAND_NAVY,
        )
        self._status_lbl.pack(side="right")

    # ── Install thread ───────────────────────────────────────────────────────

    def _start_install(self):
        self._install_btn.configure(state="disabled")
        self._cancel_btn.configure(state="disabled")
        threading.Thread(target=self._run_install, daemon=True).start()

    def _run_install(self):
        try:
            self._installer.run_all(progress_cb=self._on_step)
            self.root.after(0, self._on_success)
        except Exception as exc:
            self.root.after(0, self._on_failure, str(exc))

    def _on_step(self, idx: int, name: str, success: bool, error):
        def update():
            if success:
                self._step_vars[idx].set(f"  ✓  {name}")
                self._step_labels[idx].configure(fg=BRAND_GREEN)
            else:
                self._step_vars[idx].set(f"  ✗  {name}")
                self._step_labels[idx].configure(fg=BRAND_RED)
            self._progress["value"] = idx + 1

        self.root.after(0, update)

    def _on_success(self):
        self._status_lbl.configure(text="Instalación completada", fg=BRAND_GREEN)
        self._cancel_btn.configure(state="normal", text="Cerrar")
        messagebox.showinfo(
            "AR Electronics",
            "Instalación completada con éxito.\n\n"
            "AR-ECDIS y AR-Autopilot están listos.\n"
            "Reinicie el equipo para activar el inicio automático.",
        )

    def _on_failure(self, msg: str):
        self._status_lbl.configure(text="Error en la instalación", fg=BRAND_RED)
        self._install_btn.configure(state="normal")
        self._cancel_btn.configure(state="normal")
        messagebox.showerror(
            "Error de instalación",
            f"La instalación no se completó:\n\n{msg}\n\n"
            "Verifique la conexión a internet y vuelva a intentarlo.\n"
            "Si el problema persiste, contacte a AR Electronics.",
        )

    # ── Log output ───────────────────────────────────────────────────────────

    def _append_log(self, text: str):
        def _do():
            self._log_text.configure(state="normal")
            self._log_text.insert("end", text + "\n")
            self._log_text.see("end")
            self._log_text.configure(state="disabled")

        self.root.after(0, _do)

    # ── Main loop ────────────────────────────────────────────────────────────

    def run(self):
        self.root.mainloop()


# ---------------------------------------------------------------------------
#  Silent / headless mode
# ---------------------------------------------------------------------------

def run_silent():
    installer = Installer()
    errors = []

    def cb(idx, name, success, error):
        icon = "✓" if success else "✗"
        print(f"  [{icon}] {name}")
        if error:
            errors.append(str(error))

    try:
        installer.run_all(progress_cb=cb)
        print("\nInstalación completada con éxito.")
    except Exception as exc:
        print(f"\nERROR: {exc}")
        sys.exit(1)


# ---------------------------------------------------------------------------
#  Entry point
# ---------------------------------------------------------------------------

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="AR Electronics J6412 Installer")
    parser.add_argument(
        "--silent", action="store_true", help="Run without GUI (headless mode)"
    )
    parser.add_argument(
        "--no-admin-check", action="store_true", help="Skip UAC elevation request"
    )
    args = parser.parse_args()

    if not args.no_admin_check:
        require_admin()

    if args.silent:
        run_silent()
    else:
        InstallerWindow().run()
