#!/usr/bin/env python3
"""
OASIS Control Panel v3 — WebKit2GTK
UI en HTML/CSS/JS dentro de ventana GTK nativa.
Sin interferencias del tema GTK — control visual total como Electron.
"""

import gi
gi.require_version('Gtk', '3.0')

# Intentar WebKit2 4.1 primero, luego 4.0
_wk = None
for _v in ('4.1', '4.0'):
    try:
        gi.require_version('WebKit2', _v)
        _wk = _v
        break
    except ValueError:
        pass
if _wk is None:
    print("ERROR: WebKit2GTK no encontrado. Instala: gir1.2-webkit2-4.0")
    raise SystemExit(1)

from gi.repository import Gtk, WebKit2, GLib, Gdk

import subprocess
import sys
import signal
import threading
import json
from pathlib import Path

# ── Rutas base ─────────────────────────────────────────────────────────────
SCRIPT_DIR   = Path(__file__).parent.resolve()
INSTALLER_SH = SCRIPT_DIR / "installer.sh"
ECOIN_DIR    = Path.home() / "ecoin"

# Estado persistente (mismo fichero que usa el installer original)
_STATE_FILE  = Path.home() / ".config" / "oasis-installer" / "state"

# Candidatos donde puede estar OASIS instalado
_OASIS_CANDIDATES = [
    Path.home() / "COFRE" / "CODERS" / "oasis",
    Path.home() / "oasis",
    Path.home() / "Projects" / "oasis",
    Path.home() / "Proyectos" / "oasis",
    Path.home() / "Documentos" / "oasis",
    Path.home() / "Documents" / "oasis",
    Path.home() / "src" / "oasis",
]

# ── Detección OASIS ────────────────────────────────────────────────────────
def _oasis_valid(d: Path) -> bool:
    """Flexible: cualquiera de estas señales indica que OASIS está ahí."""
    return (
        (d / "oasis.sh").is_file()
        or (d / "src" / "server" / "server.js").is_file()
        or (d / "src" / "server" / "node_modules").is_dir()
    )

def _read_saved_dir() -> Path | None:
    """Lee la ruta guardada por el installer original."""
    if _STATE_FILE.is_file():
        for line in _STATE_FILE.read_text().splitlines():
            if line.startswith("OASIS_DIR="):
                p = Path(line[10:].strip())
                if p.is_dir():
                    return p
    return None

def find_oasis_dir() -> Path:
    """Devuelve la ruta de OASIS encontrada o ~/oasis como fallback."""
    candidates = []
    saved = _read_saved_dir()
    if saved:
        candidates.append(saved)
    candidates.extend(_OASIS_CANDIDATES)
    for d in candidates:
        if d.is_dir() and _oasis_valid(d):
            return d
    return Path.home() / "oasis"

def oasis_installed() -> bool:
    return _oasis_valid(find_oasis_dir())

def oasis_version() -> str:
    pkg = find_oasis_dir() / "src" / "server" / "package.json"
    if pkg.is_file():
        try:
            return json.loads(pkg.read_text()).get("version", "")
        except Exception:
            pass
    return ""

def oasis_running(port: int = 3000) -> bool:
    """Comprueba si hay algo escuchando en el puerto de OASIS (igual que el installer)."""
    try:
        r = subprocess.run(
            ["ss", "-lnt", f"sport = :{port}"],
            capture_output=True, text=True, timeout=2
        )
        return str(port) in r.stdout
    except Exception:
        return False

# ── Detección ECOIN ────────────────────────────────────────────────────────
_ECOIN_CANDIDATES = [
    Path.home() / "ecoin",
    Path.home() / "COFRE" / "CODERS" / "ecoin",
    Path.home() / "Projects" / "ecoin",
]

def find_ecoin_dir() -> Path:
    for d in _ECOIN_CANDIDATES:
        if (d / "ecoin" / "ecoin-qt").is_file() or \
           (d / "ecoin" / "src" / "ecoind").is_file():
            return d
    return Path.home() / "ecoin"

def ecoin_installed() -> bool:
    d = find_ecoin_dir()
    return (d / "ecoin" / "ecoin-qt").is_file() or \
           (d / "ecoin" / "src" / "ecoind").is_file()

def ecoin_wallet_exists() -> bool:
    return (Path.home() / ".ecoin" / "wallet.dat").is_file()

def node_version() -> str:
    try:
        r = subprocess.run(["node", "--version"],
                           capture_output=True, text=True, timeout=3)
        return r.stdout.strip()
    except Exception:
        return ""


# ── Panel principal ────────────────────────────────────────────────────────
class OasisPanel:

    def __init__(self):
        self.win = Gtk.Window()
        self.win.set_title("SOLAR NET HUB")
        self.win.set_default_size(390, 620)
        self.win.set_resizable(False)
        self.win.set_position(Gtk.WindowPosition.CENTER)
        self.win.connect("destroy", Gtk.main_quit)

        # Fondo negro en la ventana GTK para evitar parpadeo blanco al cargar
        css = b"window { background-color: #000000; }"
        provider = Gtk.CssProvider()
        provider.load_from_data(css)
        self.win.get_style_context().add_provider(
            provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        )

        # ── WebKit2 ───────────────────────────────────────────────────────
        self.manager = WebKit2.UserContentManager()
        self.manager.connect("script-message-received::bridge", self._on_message)
        self.manager.register_script_message_handler("bridge")

        settings = WebKit2.Settings()
        settings.set_enable_javascript(True)
        settings.set_enable_developer_extras(True)   # activa inspector por si hace falta depurar
        settings.set_allow_file_access_from_file_urls(True)
        settings.set_allow_universal_access_from_file_urls(True)
        settings.set_enable_page_cache(False)        # sin caché de página

        self.webview = WebKit2.WebView.new_with_user_content_manager(self.manager)
        self.webview.set_settings(settings)
        self.webview.set_background_color(Gdk.RGBA(0, 0, 0, 1))

        self.win.add(self.webview)

        # Cargar HTML directamente por URI (evita caché de recursos)
        html_file = SCRIPT_DIR / "ui" / "index.html"
        self.webview.load_uri(f"file://{html_file}")

        self.win.show_all()

        # Poll inicial tras 1.5 s (tiempo para que cargue el HTML)
        GLib.timeout_add(1500, self._initial_poll)
        # Poll continuo cada 3 s
        GLib.timeout_add_seconds(3, self._poll)

    # ── Polling ───────────────────────────────────────────────────────────
    def _initial_poll(self):
        self._poll()
        return False

    def _poll(self):
        threading.Thread(target=self._poll_thread, daemon=True).start()
        return True

    def _poll_thread(self):
        o_dir  = find_oasis_dir()
        o_inst = _oasis_valid(o_dir)
        o_run  = oasis_running()
        o_ver  = oasis_version()
        o_node = node_version()
        e_dir  = find_ecoin_dir()
        e_inst = ecoin_installed()
        e_wall = ecoin_wallet_exists()
        e_qt   = (e_dir / "ecoin" / "ecoin-qt").is_file()
        e_dmn  = (e_dir / "ecoin" / "src" / "ecoind").is_file()

        data = {
            "oasis_installed": o_inst,
            "oasis_running":   o_run,
            "oasis_version":   o_ver,
            "node_version":    o_node,
            "oasis_dir":       str(o_dir) if o_inst else "",
            "ecoin_installed": e_inst,
            "ecoin_wallet":    e_wall,
            "ecoin_qt":        e_qt,
            "ecoin_daemon":    e_dmn,
            "ecoin_dir":       str(e_dir) if e_inst else "",
        }
        GLib.idle_add(self._js, f"updateStatus({json.dumps(data)})")

    # ── JS helpers ────────────────────────────────────────────────────────
    def _js(self, code):
        # evaluate_javascript (WebKit2 4.1+) o run_javascript (4.0 fallback)
        if hasattr(self.webview, "evaluate_javascript"):
            self.webview.evaluate_javascript(code, -1, None, None, None, None, None)
        else:
            self.webview.run_javascript(code, None, None, None)
        return False

    def _log(self, text):
        self._js(f"appendLog({json.dumps(text)})")

    # ── Mensaje desde JS ──────────────────────────────────────────────────
    def _on_message(self, _manager, result):
        try:
            data   = json.loads(result.get_value().to_string())
            action = data.get("action", "")
        except Exception:
            return
        self._dispatch(action)

    def _dispatch(self, action):
        installer_cmds = {
            "oasis-install":  ["--oasis-install"],
            "ecoin-install":  ["--ecoin-install"],
            "ecoin-gui":      ["--ecoin-gui"],
            "ecoin-wallet":   ["--wallet-create"],
            "ecoin-connect":  ["--wallet-connect"],
        }

        if action == "oasis-stop":
            threading.Thread(target=self._kill_oasis, daemon=True).start()
            GLib.idle_add(self._js, "switchTab('sistema')")

        elif action == "oasis-start":
            GLib.idle_add(self._js, "switchTab('sistema')")
            threading.Thread(target=self._start_oasis, daemon=True).start()

        elif action == "oasis-browser":
            subprocess.Popen(["xdg-open", "http://localhost:3000"])
            GLib.idle_add(self._log, "Abriendo http://localhost:3000 ...")

        elif action in installer_cmds:
            GLib.idle_add(self._js, "switchTab('sistema')")
            args = installer_cmds[action]
            threading.Thread(target=self._run_cmd, args=(args,), daemon=True).start()

    def _run_cmd(self, args):
        cmd = [str(INSTALLER_SH)] + args
        GLib.idle_add(self._log, "$ " + " ".join(cmd))
        try:
            proc = subprocess.Popen(
                cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                text=True, bufsize=1
            )
            for line in iter(proc.stdout.readline, ""):
                line = line.rstrip()
                if line:
                    GLib.idle_add(self._log, line)
            proc.wait()
            GLib.idle_add(self._log, f"── fin (código {proc.returncode}) ──")
        except Exception as e:
            GLib.idle_add(self._log, f"[Error]: {e}")
        GLib.idle_add(self._poll)

    def _start_oasis(self):
        import time
        PORT = 3000
        oasis_dir = find_oasis_dir()
        oasis_sh  = oasis_dir / "oasis.sh"

        if not oasis_sh.is_file():
            GLib.idle_add(self._log, f"[Error]: no se encontró oasis.sh en {oasis_dir}")
            return

        # ¿Ya está escuchando?
        if oasis_running(PORT):
            GLib.idle_add(self._log, f"OASIS ya está escuchando en el puerto {PORT}")
            subprocess.Popen(["xdg-open", f"http://localhost:{PORT}"])
            GLib.idle_add(self._poll)
            return

        # Lanzar en background igual que el installer original
        cmd = f'cd "{oasis_dir}" && exec bash oasis.sh'
        GLib.idle_add(self._log, f"$ nohup bash -lc '{cmd}' &")
        try:
            subprocess.Popen(
                ["bash", "-lc", cmd],
                stdout=open("/tmp/oasis_gui.log", "w"),
                stderr=subprocess.STDOUT,
                start_new_session=True,
            )
        except Exception as e:
            GLib.idle_add(self._log, f"[Error al lanzar]: {e}")
            return

        # Esperar hasta 30 s a que el puerto esté activo
        GLib.idle_add(self._log, "Esperando que OASIS levante en el puerto 3000...")
        for i in range(30):
            time.sleep(1)
            if oasis_running(PORT):
                GLib.idle_add(self._log, f"OASIS levantado en {PORT}. Abriendo navegador...")
                subprocess.Popen(["xdg-open", f"http://localhost:{PORT}"])
                GLib.idle_add(self._poll)
                return
            if (i + 1) % 5 == 0:
                GLib.idle_add(self._log, f"  ... {i+1}s")

        GLib.idle_add(self._log, "[Error]: OASIS no levantó en 30 s. Revisa /tmp/oasis_gui.log")
        GLib.idle_add(self._poll)

    def _kill_oasis(self):
        GLib.idle_add(self._log, "Deteniendo OASIS...")
        try:
            subprocess.run(["pkill", "-f", "node.*server.js"], timeout=5)
            GLib.idle_add(self._log, "Servidor OASIS detenido.")
        except Exception as e:
            GLib.idle_add(self._log, f"[Error]: {e}")
        GLib.idle_add(self._poll)


# ── Main ───────────────────────────────────────────────────────────────────
def main():
    signal.signal(signal.SIGINT, signal.SIG_DFL)
    OasisPanel()
    Gtk.main()


if __name__ == "__main__":
    main()
