#!/usr/bin/env python3
"""
instalar.py - Instalador universal de SiPi (Linux, Mac, Windows).

Objetivo: correr "python3 instalar.py" (o "python instalar.py" en
Windows) UNA vez, sin ningun paso manual mas, y quedar con el comando
`sipi` (y `sipi-editor`, `sipi-cli`, `sipi-kids`, `sipi-ia`,
`sipi-ia-servidor`, `sipi-dev`, `sipi-lsp`) funcionando desde
cualquier carpeta, en cualquier terminal nueva.

No requiere pip, no requiere permisos de administrador/sudo, no toca
nada fuera de la carpeta del usuario. No es un reemplazo del
empaquetado Debian real (ver PACKAGING_VINARI.md) -- es la via rapida
para alguien que bajo el codigo fuente y quiere el comando funcionando
YA, en Windows, Mac o Linux, sin depender de ningun formato de paquete
de sistema.

Que hace, en orden (todo se imprime en pantalla a medida que pasa,
nada se hace en silencio -- ver seccion 5 del pedido: "transparencia"):
  1. Verifica que haya un Python 3.10+ disponible (motor de SiPi lo
     requiere) y que 'tkinter' este disponible para los editores
     graficos (si falta, igual instala el interprete/CLI y avisa que
     falta 'python3-tk'/'tkinter' para el resto).
  2. Copia todo 'src/', 'examples/' y 'docs/' a una carpeta propia del
     usuario (no del sistema):
       - Linux/Mac:  ~/.local/share/sipi
       - Windows:    %LOCALAPPDATA%\\SiPi
  3. Genera los lanzadores (sipi, sipi-editor, sipi-cli, sipi-kids,
     sipi-ia, sipi-ia-servidor, sipi-dev, sipi-lsp) apuntando a esa
     carpeta, y los deja en:
       - Linux/Mac:  ~/.local/bin
       - Windows:    %LOCALAPPDATA%\\SiPi\\bin
  4. Si esa carpeta de lanzadores no esta en el PATH todavia, lo deja
     agregado para las proximas terminales (en Windows via el PATH de
     usuario persistente con 'setx'; en Linux/Mac agregando una linea
     al perfil de shell que corresponda) y lo dice explicitamente --
     nunca lo hace sin avisar.
  5. En Linux, ademas intenta registrar los `.desktop` y las paginas de
     manual en las carpetas de usuario (~/.local/share/applications,
     ~/.local/share/man/man1) para que aparezcan en el menu de
     aplicaciones y en 'man sipi' -- best-effort, no rompe nada si no
     se puede.

Se puede correr de nuevo en cualquier momento (por ejemplo tras bajar
una version mas nueva del codigo fuente): vuelve a copiar todo y deja
todo al dia, no acumula copias viejas.

Desinstalar: 'python3 instalar.py --desinstalar' borra exactamente lo
que este mismo script creo (y nada mas).
"""
import os
import sys
import shutil
import stat
import platform
import subprocess
import textwrap

VERSION_MINIMA_PYTHON = (3, 10)

NOMBRE_A_ARCHIVO = {
    "sipi": "sipi.py",
    "sipi-editor": "editor_sipi.py",
    "sipi-cli": "sipi_cli.py",
    "sipi-kids": "editor_sipi_kids.py",
    "sipi-ia": "sipi_ia_agente.py",
    "sipi-ia-servidor": "sipi_ia_servidor.py",
    "sipi-dev": "editor_sipi_dev.py",
    "sipi-lsp": "sipi_lsp.py",
    "publicar": "publicar.py",
    "sipi-world-servidor": "sipi_world_servidor.py",
}

RAIZ_REPO = os.path.dirname(os.path.abspath(__file__))


def _log(mensaje):
    print(f"[SiPi] {mensaje}")


def _es_windows():
    return os.name == "nt"


def _carpeta_instalacion():
    if _es_windows():
        base = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~\\AppData\\Local")
        return os.path.join(base, "SiPi")
    return os.path.join(os.path.expanduser("~"), ".local", "share", "sipi")


def _carpeta_lanzadores():
    if _es_windows():
        return os.path.join(_carpeta_instalacion(), "bin")
    return os.path.join(os.path.expanduser("~"), ".local", "bin")


def _verificar_python():
    version = sys.version_info
    if (version.major, version.minor) < VERSION_MINIMA_PYTHON:
        _log(f"ERROR: SiPi necesita Python {VERSION_MINIMA_PYTHON[0]}.{VERSION_MINIMA_PYTHON[1]}+ "
             f"y esta corriendo con {platform.python_version()}.")
        _log("Instala una version mas nueva de Python y volve a correr este instalador.")
        raise SystemExit(1)
    _log(f"Python {platform.python_version()} - OK (minimo requerido: "
         f"{VERSION_MINIMA_PYTHON[0]}.{VERSION_MINIMA_PYTHON[1]})")

    try:
        import tkinter  # noqa: F401
        _log("tkinter (interfaz grafica) - OK")
    except ImportError:
        _log("AVISO: 'tkinter' no esta disponible en este Python.")
        _log("       El interprete ('sipi') y la CLI ('sipi-cli') van a funcionar igual,")
        _log("       pero los editores graficos (sipi-editor, sipi-kids, sipi-dev) no van a abrir")
        _log("       hasta que lo instales. En Debian/Ubuntu: sudo apt install python3-tk")


def _copiar_arbol(origen, destino):
    if os.path.isdir(destino):
        shutil.rmtree(destino)
    shutil.copytree(origen, destino)


def _instalar_archivos():
    destino = _carpeta_instalacion()
    _log(f"Copiando el codigo fuente a: {destino}")
    os.makedirs(destino, exist_ok=True)
    _copiar_arbol(os.path.join(RAIZ_REPO, "src"), os.path.join(destino, "src"))
    if os.path.isdir(os.path.join(RAIZ_REPO, "examples")):
        _copiar_arbol(os.path.join(RAIZ_REPO, "examples"), os.path.join(destino, "examples"))
    if os.path.isdir(os.path.join(RAIZ_REPO, "docs")):
        _copiar_arbol(os.path.join(RAIZ_REPO, "docs"), os.path.join(destino, "docs"))
    return destino


PLANTILLA_UNIX = """#!/bin/sh
# Generado por instalar.py -- no editar a mano (se pisa en cada instalacion).
export PYTHONDONTWRITEBYTECODE=1
exec {python} "{script}" "$@"
"""

PLANTILLA_WINDOWS = """@echo off
rem Generado por instalar.py -- no editar a mano (se pisa en cada instalacion).
"{python}" "{script}" %*
"""


def _generar_lanzadores(carpeta_instalada):
    carpeta_bin = _carpeta_lanzadores()
    os.makedirs(carpeta_bin, exist_ok=True)
    _log(f"Generando comandos en: {carpeta_bin}")
    python_actual = sys.executable
    creados = []
    for nombre, archivo in NOMBRE_A_ARCHIVO.items():
        ruta_script = os.path.join(carpeta_instalada, "src", archivo)
        if not os.path.exists(ruta_script):
            _log(f"  (omitido) {nombre}: no se encontro {archivo} en src/")
            continue
        if _es_windows():
            ruta_lanzador = os.path.join(carpeta_bin, nombre + ".bat")
            contenido = PLANTILLA_WINDOWS.format(python=python_actual, script=ruta_script)
        else:
            ruta_lanzador = os.path.join(carpeta_bin, nombre)
            contenido = PLANTILLA_UNIX.format(python=python_actual, script=ruta_script)
        with open(ruta_lanzador, "w", encoding="utf-8", newline="\n") as f:
            f.write(contenido)
        if not _es_windows():
            modo = os.stat(ruta_lanzador).st_mode
            os.chmod(ruta_lanzador, modo | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
        creados.append(nombre)
        _log(f"  {nombre} -> {archivo}")
    return creados


def _path_actual_incluye(carpeta):
    carpeta_norm = os.path.normcase(os.path.normpath(carpeta))
    return any(
        os.path.normcase(os.path.normpath(p)) == carpeta_norm
        for p in os.environ.get("PATH", "").split(os.pathsep) if p
    )


def _agregar_al_path_windows(carpeta_bin):
    try:
        resultado = subprocess.run(
            ["reg", "query", "HKCU\\Environment", "/v", "PATH"],
            capture_output=True, text=True, check=False)
        path_usuario_actual = ""
        if resultado.returncode == 0:
            for linea in resultado.stdout.splitlines():
                if "PATH" in linea and "REG_" in linea:
                    path_usuario_actual = linea.split("REG_EXPAND_SZ", 1)[-1].split("REG_SZ", 1)[-1].strip()
        nuevo_path = f"{path_usuario_actual};{carpeta_bin}" if path_usuario_actual else carpeta_bin
        subprocess.run(["setx", "PATH", nuevo_path], capture_output=True, text=True, check=False)
        _log(f"Se agrego '{carpeta_bin}' al PATH de usuario (persistente).")
        _log("Abri una terminal NUEVA para que el cambio tenga efecto.")
        return True
    except Exception as e:
        _log(f"No se pudo agregar automaticamente al PATH ({e}).")
        return False


def _agregar_al_path_unix(carpeta_bin):
    shell = os.environ.get("SHELL", "")
    if "zsh" in shell:
        archivo_perfil = os.path.join(os.path.expanduser("~"), ".zshrc")
    elif "fish" in shell:
        archivo_perfil = os.path.join(os.path.expanduser("~"), ".config", "fish", "config.fish")
    else:
        archivo_perfil = os.path.join(os.path.expanduser("~"), ".bashrc")

    linea_export = f'export PATH="{carpeta_bin}:$PATH"'
    if "fish" in shell:
        linea_export = f'set -gx PATH "{carpeta_bin}" $PATH'

    try:
        contenido_previo = ""
        if os.path.exists(archivo_perfil):
            with open(archivo_perfil, "r", encoding="utf-8") as f:
                contenido_previo = f.read()
        if carpeta_bin in contenido_previo:
            _log(f"'{archivo_perfil}' ya tenia '{carpeta_bin}' en el PATH -- no se toco de nuevo.")
            return True
        with open(archivo_perfil, "a", encoding="utf-8") as f:
            f.write(f"\n# Agregado por el instalador de SiPi ({carpeta_bin})\n{linea_export}\n")
        _log(f"Se agrego '{carpeta_bin}' al PATH en '{archivo_perfil}'.")
        _log("Abri una terminal nueva (o corre 'source " + archivo_perfil + "') para que tenga efecto.")
        return True
    except Exception as e:
        _log(f"No se pudo editar '{archivo_perfil}' automaticamente ({e}).")
        return False


def _asegurar_path(carpeta_bin, sin_modificar_shell):
    if _path_actual_incluye(carpeta_bin):
        _log(f"'{carpeta_bin}' ya esta en el PATH. Los comandos ya deberian funcionar.")
        return
    if sin_modificar_shell:
        _log(f"AVISO: '{carpeta_bin}' no esta en el PATH todavia.")
        _log("       Se corrio con --sin-modificar-shell, asi que no se toco ningun archivo de shell.")
        _log(f"       Agregalo vos a mano, por ejemplo: export PATH=\"{carpeta_bin}:$PATH\"")
        return
    if _es_windows():
        _agregar_al_path_windows(carpeta_bin)
    else:
        _agregar_al_path_unix(carpeta_bin)


def _registrar_integracion_escritorio_linux(carpeta_instalada):
    """Best-effort: copia los .desktop y paginas de manual a las carpetas
    de USUARIO (nunca del sistema, no hace falta sudo). Si algo falla no
    interrumpe la instalacion -- el comando 'sipi' ya funciona sin esto."""
    if _es_windows() or platform.system() == "Darwin":
        return
    try:
        origen_desktop = os.path.join(RAIZ_REPO, "pkg-assets", "desktop")
        destino_desktop = os.path.join(os.path.expanduser("~"), ".local", "share", "applications")
        if os.path.isdir(origen_desktop):
            os.makedirs(destino_desktop, exist_ok=True)
            for archivo in os.listdir(origen_desktop):
                origen = os.path.join(origen_desktop, archivo)
                destino = os.path.join(destino_desktop, archivo)
                with open(origen, "r", encoding="utf-8") as f:
                    contenido = f.read()
                # Los .desktop del repo asumen 'sipi-editor' ya en PATH,
                # que es exactamente lo que este instalador garantiza.
                with open(destino, "w", encoding="utf-8") as f:
                    f.write(contenido)
            _log(f"Iconos de aplicacion instalados en {destino_desktop} (menu de aplicaciones)")

        origen_man = os.path.join(RAIZ_REPO, "pkg-assets", "man")
        destino_man = os.path.join(os.path.expanduser("~"), ".local", "share", "man", "man1")
        if os.path.isdir(origen_man):
            os.makedirs(destino_man, exist_ok=True)
            for archivo in os.listdir(origen_man):
                shutil.copy(os.path.join(origen_man, archivo), os.path.join(destino_man, archivo))
            _log(f"Paginas de manual instaladas en {destino_man} (ej: 'man sipi')")
    except Exception as e:
        _log(f"(no critico) No se pudo registrar la integracion de escritorio: {e}")


def desinstalar():
    _log("Desinstalando SiPi (solo lo que instalo este mismo script)...")
    carpeta_instalada = _carpeta_instalacion()
    carpeta_bin = _carpeta_lanzadores()
    if os.path.isdir(carpeta_instalada):
        shutil.rmtree(carpeta_instalada)
        _log(f"Borrado: {carpeta_instalada}")
    for nombre in NOMBRE_A_ARCHIVO:
        for candidato in (nombre, nombre + ".bat"):
            ruta = os.path.join(carpeta_bin, candidato)
            if os.path.exists(ruta):
                os.remove(ruta)
                _log(f"Borrado: {ruta}")
    _log("Los cambios al PATH (linea en .bashrc/.zshrc, o PATH de usuario en Windows) no se")
    _log("revierten automaticamente para no arriesgar tocar el perfil de shell sin que lo pidas;")
    _log("son inofensivos si quedan (apuntan a una carpeta que ya no existe).")
    _log("Listo.")


def main():
    if "--desinstalar" in sys.argv:
        return desinstalar()

    sin_modificar_shell = "--sin-modificar-shell" in sys.argv

    print()
    _log("Instalador universal de SiPi")
    _log("=" * 40)
    _verificar_python()
    carpeta_instalada = _instalar_archivos()
    creados = _generar_lanzadores(carpeta_instalada)
    _asegurar_path(_carpeta_lanzadores(), sin_modificar_shell)
    _registrar_integracion_escritorio_linux(carpeta_instalada)

    print()
    _log("Instalacion terminada.")
    if creados:
        _log("Comandos disponibles (en una terminal nueva): " + ", ".join(creados))
    _log("Probalo con: sipi --ayuda")
    _log("Para desinstalar en cualquier momento: python3 instalar.py --desinstalar")
    print()


if __name__ == "__main__":
    main()
