"""Build script para empaquetar la companion como `.exe` con PyInstaller.

Uso:
    cd companion
    pip install -e ".[tray,gui,build]"
    python build.py

Salida: `dist/MaxEditorCompanion.exe` (~30-60 MB).

Decisiones:
- `--onefile`: un único `.exe` portable. Tradeoff: arranque ~1-2s más
  lento (extrae a temp), pero deployment trivial. Para production con
  muchos arranques, considerar `--onedir` + instalador NSIS.
- `--windowed`: sin consola flotando. Stderr/stdout van a /dev/null;
  los logs van al rotating file handler en `%LOCALAPPDATA%\\MaxEditor\\logs`.
- `--name MaxEditorCompanion`: nombre del binario.
- `--icon`: usamos el `assets/icon.ico` si existe; si no, PyInstaller
  pone uno default (no fatal).
- `--hidden-import`: pystray y Pillow tienen imports dinámicos que
  PyInstaller a veces no detecta. Forzamos.
- `--collect-data cryptography`: por compat con OpenSSL bundles.

Verificación post-build:
- Smoke: `dist\\MaxEditorCompanion.exe` arranca, escribe log, responde
  /health en el puerto configurado.
- Validación opcional con `dumpbin /dependents` para ver DLLs faltantes
  (no la corremos en el script porque requiere Visual Studio Build Tools).
"""

from __future__ import annotations

import shutil
import subprocess
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent
SRC_ENTRY = ROOT / "src" / "maxeditor_companion" / "__main__.py"
SRC_PACKAGE_ROOT = ROOT / "src"
ASSETS_DIR = ROOT / "assets"
ICON_PATH = ASSETS_DIR / "icon.ico"
DIST_DIR = ROOT / "dist"
BUILD_DIR = ROOT / "build"
SPEC_FILE = ROOT / "MaxEditorCompanion.spec"

EXE_NAME = "MaxEditorCompanion"

# Imports que PyInstaller no detecta automáticamente.
HIDDEN_IMPORTS = (
    "pystray._win32",
    "PIL._tkinter_finder",
    "uvicorn.logging",
    "uvicorn.loops.auto",
    "uvicorn.protocols.http.auto",
    "uvicorn.protocols.websockets.auto",
    "uvicorn.lifespan.on",
    # PyWebView (Fase 1.8): backends dinámicos según plataforma.
    "webview",
    "webview.platforms.edgechromium",
    "webview.platforms.winforms",
    "clr_loader",
)

# Paquetes con data files que necesitan `--collect-data`.
COLLECT_DATA = (
    "cryptography",
    # PyWebView trae JS/HTML embebido y el bridge nativo.
    "webview",
)

# Módulos pesados a excluir del bundle. El companion incluye servicios
# de transcripción local (`services/transcribe.py`) que importan torch
# + faster_whisper. PyInstaller detecta esos imports en el AST aunque
# sean lazy adentro de funciones, e incluiría ~2.8 GB de binarios.
# Para el flujo SaaS (Companion -> Django remoto), la transcripción
# corre en el server: NO necesitamos esos paquetes en el .exe.
EXCLUDE_MODULES = (
    "torch",
    "torchvision",
    "torchaudio",
    "tensorboard",
    "faster_whisper",
    "whisper",
    "transformers",
    "scipy",
    "numpy.testing",
    "pandas",
    "matplotlib",
    "IPython",
    "jupyter",
    "notebook",
    "pytest",
    "sphinx",
)


def main() -> int:
    if not SRC_ENTRY.exists():
        print(f"[error] entry point no encontrado: {SRC_ENTRY}", file=sys.stderr)
        return 2

    try:
        import PyInstaller  # noqa: F401
    except ImportError:
        print("[error] PyInstaller no está instalado. Ejecuta: pip install -e \".[tray,build]\"", file=sys.stderr)
        return 2

    # Limpieza previa.
    for path in (DIST_DIR, BUILD_DIR, SPEC_FILE):
        if path.exists():
            if path.is_dir():
                print(f"[clean] removing {path}")
                shutil.rmtree(path, ignore_errors=True)
            else:
                print(f"[clean] removing {path}")
                path.unlink(missing_ok=True)

    cmd: list[str] = [
        sys.executable, "-m", "PyInstaller",
        "--noconfirm",
        "--clean",
        "--onefile",
        "--windowed",
        f"--name={EXE_NAME}",
        f"--distpath={DIST_DIR}",
        f"--workpath={BUILD_DIR}",
        f"--specpath={ROOT}",
    ]
    if ICON_PATH.exists():
        cmd.append(f"--icon={ICON_PATH}")
    else:
        print(f"[warn] icon no encontrado en {ICON_PATH} — usando default de PyInstaller")

    for hidden in HIDDEN_IMPORTS:
        cmd.append(f"--hidden-import={hidden}")
    for pkg in COLLECT_DATA:
        cmd.append(f"--collect-data={pkg}")
    for mod in EXCLUDE_MODULES:
        cmd.append(f"--exclude-module={mod}")

    # Que PyInstaller pueda resolver `import maxeditor_companion` desde src/.
    cmd.append(f"--paths={SRC_PACKAGE_ROOT}")

    cmd.append(str(SRC_ENTRY))

    print("[build] running:", " ".join(cmd))
    result = subprocess.run(cmd, cwd=ROOT)
    if result.returncode != 0:
        print(f"[error] PyInstaller falló con código {result.returncode}", file=sys.stderr)
        return result.returncode

    exe_path = DIST_DIR / f"{EXE_NAME}.exe"
    if not exe_path.exists():
        # En Linux/macOS no hay `.exe`.
        alt = DIST_DIR / EXE_NAME
        if alt.exists():
            exe_path = alt
        else:
            print(f"[error] no se encontró el binario en {DIST_DIR}", file=sys.stderr)
            return 1

    size_mb = exe_path.stat().st_size / (1024 * 1024)
    print()
    print(f"[ok] build completo: {exe_path}  ({size_mb:.1f} MB)")
    print()
    print("Próximos pasos:")
    print(f"  1) Smoke local:   {exe_path}")
    print(f"  2) Smoke headless con HTTP: COMPANION_USE_SSL=0 COMPANION_HEADLESS=1 {exe_path}")
    print( "  3) Distribuir: copiar el .exe a la PC objetivo. La companion auto-instala")
    print( "                 el cert en CurrentUser\\Root al primer arranque (no requiere admin).")
    return 0


if __name__ == "__main__":
    sys.exit(main())
