#!/usr/bin/env python3
"""
OASIS Control Panel — Solar Net Hub
Panel de control compacto estilo Mullvad para gestionar OASIS y ECOIN en Linux.
"""

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib, Gdk, GdkPixbuf

import subprocess
import os
import sys
import threading
import json
import shutil
from pathlib import Path

# ── Rutas ──────────────────────────────────────────────────────────────────
SCRIPT_DIR   = Path(__file__).parent.resolve()
INSTALLER_SH = SCRIPT_DIR / "installer.sh"
OASIS_DIR    = Path.home() / "oasis"
ECOIN_DIR    = Path.home() / "ecoin"
MODEL_FILE   = "oasis-42-1-chat.Q4_K_M.gguf"

# ── CSS ────────────────────────────────────────────────────────────────────
CSS = """
* {
    font-family: "Dune Rise", "Cantarell", "Ubuntu", "DejaVu Sans", sans-serif;
    color: #E6E6E6;
}

window {
    background-color: #000000;
}

/* Header */
box.panel-header {
    background-color: #080808;
    border-bottom: 1px solid #1C1C1C;
}

label.panel-title {
    font-size: 11pt;
    font-weight: bold;
    color: #FF4E00;
    letter-spacing: 3px;
}

/* Status card */
box.status-card {
    background-color: #0D0D0D;
    border: 1px solid #1C1C1C;
    border-radius: 12px;
}

label.status-main {
    font-size: 14pt;
    font-weight: bold;
}

label.state-running { color: #27D980; }
label.state-stopped { color: #FF4E00; }
label.state-unknown { color: #555555; }

label.status-sub {
    font-size: 8pt;
    color: #666666;
}

/* Dots */
label.dot { font-size: 9pt; }
label.dot-running { color: #27D980; }
label.dot-stopped { color: #FF4E00; }
label.dot-unknown { color: #333333; }

/* Botones */
button.btn {
    background-color: #000000;
    color: #FF4E00;
    border: 1px solid #FF4E00;
    border-radius: 18px;
    padding: 8px 16px;
    margin: 5px 6px;
    font-size: 8pt;
    font-weight: bold;
    letter-spacing: 1px;
    min-width: 130px;
}

button.btn:hover {
    background-color: #27D980;
    color: #000000;
    border-color: #27D980;
}

button.btn:active {
    background-color: #1fa865;
    color: #000000;
    border-color: #1fa865;
}

button.btn:disabled {
    background-color: #080808;
    color: #2A2A2A;
    border-color: #181818;
}

button.btn-primary {
    background-color: #FF4E00;
    color: #000000;
    border: 1px solid #FF4E00;
    border-radius: 18px;
    padding: 8px 16px;
    margin: 5px 6px;
    font-size: 8pt;
    font-weight: bold;
    letter-spacing: 1px;
    min-width: 130px;
}

button.btn-primary:hover {
    background-color: #27D980;
    color: #000000;
    border-color: #27D980;
}

button.btn-primary:disabled {
    background-color: #301200;
    color: #553322;
    border-color: #301200;
}

/* Notebook / Tabs */
notebook > header {
    background-color: #000000;
    border-bottom: 1px solid #1C1C1C;
    padding: 0px;
}

notebook > header tabs {
    background-color: #000000;
}

tab {
    background-color: #000000;
    color: #444444;
    border: none;
    border-bottom: 2px solid transparent;
    padding: 10px 22px;
    font-size: 8pt;
    letter-spacing: 1px;
    font-weight: bold;
}

tab:checked {
    background-color: #000000;
    color: #FF4E00;
    border-bottom: 2px solid #FF4E00;
}

tab:hover {
    color: #AAAAAA;
}

/* Info grid */
label.info-key {
    color: #555555;
    font-size: 8pt;
    min-width: 90px;
}

label.info-val {
    color: #CCCCCC;
    font-size: 8pt;
}

/* Log area */
textview.log-view {
    background-color: #050505;
    color: #27D980;
    font-family: "DejaVu Sans Mono", "Monospace", monospace;
    font-size: 7.5pt;
    padding: 8px;
}

textview.log-view text {
    background-color: #050505;
    color: #27D980;
}

scrolledwindow.log-scroll {
    border: 1px solid #1C1C1C;
    border-radius: 8px;
    margin: 4px 14px 8px 14px;
}

/* Separadores */
separator {
    background-color: #141414;
    min-height: 1px;
}
"""


# ── Helpers de estado ──────────────────────────────────────────────────────
def oasis_installed():
    return (
        (OASIS_DIR / "src" / "server" / "node_modules").is_dir()
        and (OASIS_DIR / "AI" / MODEL_FILE).is_file()
    )


def oasis_version():
    pkg = 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():
    try:
        r = subprocess.run(["pgrep", "-f", "node.*server.js"],
                           capture_output=True, timeout=2)
        return r.returncode == 0
    except Exception:
        return False


def ecoin_installed():
    return (
        (ECOIN_DIR / "ecoin" / "ecoin-qt").is_file()
        or (ECOIN_DIR / "ecoin" / "src" / "ecoind").is_file()
    )


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


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


# ── Panel principal ────────────────────────────────────────────────────────
class OasisPanel(Gtk.ApplicationWindow):

    def __init__(self, app):
        super().__init__(application=app, title="SOLAR NET HUB")
        self.set_default_size(390, 610)
        self.set_resizable(False)
        self.set_position(Gtk.WindowPosition.CENTER)

        self._build_ui()
        # Poll de estado cada 3 s
        GLib.timeout_add_seconds(3, self._trigger_poll)
        self._trigger_poll()

    # ── Construcción UI ───────────────────────────────────────────────────
    def _build_ui(self):
        root = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
        self.add(root)

        root.pack_start(self._make_header(), False, False, 0)

        self.notebook = Gtk.Notebook()
        self.notebook.set_show_border(False)
        root.pack_start(self.notebook, True, True, 0)

        self.notebook.append_page(self._make_oasis_tab(),   self._tab_lbl("OASIS"))
        self.notebook.append_page(self._make_ecoin_tab(),   self._tab_lbl("ECOIN"))
        self.notebook.append_page(self._make_sistema_tab(), self._tab_lbl("SISTEMA"))

    def _tab_lbl(self, txt):
        return Gtk.Label(label=txt)

    # ── Header ────────────────────────────────────────────────────────────
    def _make_header(self):
        box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        box.get_style_context().add_class("panel-header")
        box.set_margin_top(14)
        box.set_margin_bottom(14)
        box.set_margin_start(18)
        box.set_margin_end(18)

        logo = SCRIPT_DIR / "oasis-logo.png"
        if logo.is_file():
            try:
                pb = GdkPixbuf.Pixbuf.new_from_file_at_scale(str(logo), 30, 30, True)
                box.pack_start(Gtk.Image.new_from_pixbuf(pb), False, False, 0)
            except Exception:
                pass

        title = Gtk.Label(label="SOLAR NET HUB")
        title.get_style_context().add_class("panel-title")
        box.pack_start(title, False, False, 0)

        box.pack_start(Gtk.Box(), True, True, 0)  # spacer

        self.header_dot = Gtk.Label(label="●")
        self.header_dot.get_style_context().add_class("dot")
        self.header_dot.get_style_context().add_class("dot-unknown")
        box.pack_end(self.header_dot, False, False, 4)

        return box

    # ── Tab OASIS ─────────────────────────────────────────────────────────
    def _make_oasis_tab(self):
        scroll = Gtk.ScrolledWindow()
        scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
        scroll.add(box)

        # — Status card —
        card = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        card.get_style_context().add_class("status-card")
        card.set_margin_top(16)
        card.set_margin_bottom(4)
        card.set_margin_start(14)
        card.set_margin_end(14)
        card.set_margin_top(14)
        for edge in ("top", "bottom", "start", "end"):
            getattr(card, f"set_margin_{edge}")(14)

        row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
        row.set_margin_top(14)
        row.set_margin_start(16)
        row.set_margin_end(16)

        self.oasis_dot = Gtk.Label(label="●")
        self.oasis_dot.get_style_context().add_class("dot")
        self.oasis_dot.get_style_context().add_class("dot-unknown")
        row.pack_start(self.oasis_dot, False, False, 0)

        self.oasis_state_lbl = Gtk.Label(label="Comprobando…")
        self.oasis_state_lbl.get_style_context().add_class("status-main")
        self.oasis_state_lbl.get_style_context().add_class("state-unknown")
        row.pack_start(self.oasis_state_lbl, False, False, 0)
        card.pack_start(row, False, False, 0)

        self.oasis_sub_lbl = Gtk.Label(label="")
        self.oasis_sub_lbl.get_style_context().add_class("status-sub")
        self.oasis_sub_lbl.set_halign(Gtk.Align.START)
        self.oasis_sub_lbl.set_margin_start(16)
        self.oasis_sub_lbl.set_margin_bottom(14)
        card.pack_start(self.oasis_sub_lbl, False, False, 0)

        box.pack_start(card, False, False, 0)

        # — Botones fila 1 —
        r1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
        r1.set_halign(Gtk.Align.CENTER)
        r1.set_margin_top(10)
        self.btn_o_start   = self._btn("▶  INICIAR",   self._on_oasis_start,   primary=True)
        self.btn_o_stop    = self._btn("■  DETENER",   self._on_oasis_stop)
        r1.pack_start(self.btn_o_start, False, False, 0)
        r1.pack_start(self.btn_o_stop,  False, False, 0)
        box.pack_start(r1, False, False, 0)

        # — Botones fila 2 —
        r2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
        r2.set_halign(Gtk.Align.CENTER)
        r2.set_margin_bottom(6)
        self.btn_o_install = self._btn("⬇  INSTALAR",  self._on_oasis_install)
        self.btn_o_browser = self._btn("◎  ABRIR WEB", self._on_oasis_browser)
        r2.pack_start(self.btn_o_install, False, False, 0)
        r2.pack_start(self.btn_o_browser, False, False, 0)
        box.pack_start(r2, False, False, 0)

        # — Separator + info —
        sep = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
        sep.set_margin_top(10)
        box.pack_start(sep, False, False, 0)

        grid = Gtk.Grid()
        grid.set_column_spacing(14)
        grid.set_row_spacing(6)
        grid.set_margin_start(18)
        grid.set_margin_end(18)
        grid.set_margin_top(10)
        grid.set_margin_bottom(14)
        self.oasis_ver_val  = self._info_row(grid, 0, "Versión")
        self.oasis_node_val = self._info_row(grid, 1, "Node.js")
        self.oasis_dir_val  = self._info_row(grid, 2, "Ruta")
        box.pack_start(grid, False, False, 0)

        return scroll

    # ── Tab ECOIN ─────────────────────────────────────────────────────────
    def _make_ecoin_tab(self):
        scroll = Gtk.ScrolledWindow()
        scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
        scroll.add(box)

        # — Status card —
        card = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        card.get_style_context().add_class("status-card")
        for edge in ("top", "bottom", "start", "end"):
            getattr(card, f"set_margin_{edge}")(14)

        row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
        row.set_margin_top(14)
        row.set_margin_start(16)
        row.set_margin_end(16)

        self.ecoin_dot = Gtk.Label(label="●")
        self.ecoin_dot.get_style_context().add_class("dot")
        self.ecoin_dot.get_style_context().add_class("dot-unknown")
        row.pack_start(self.ecoin_dot, False, False, 0)

        self.ecoin_state_lbl = Gtk.Label(label="Comprobando…")
        self.ecoin_state_lbl.get_style_context().add_class("status-main")
        self.ecoin_state_lbl.get_style_context().add_class("state-unknown")
        row.pack_start(self.ecoin_state_lbl, False, False, 0)
        card.pack_start(row, False, False, 0)

        self.ecoin_sub_lbl = Gtk.Label(label="")
        self.ecoin_sub_lbl.get_style_context().add_class("status-sub")
        self.ecoin_sub_lbl.set_halign(Gtk.Align.START)
        self.ecoin_sub_lbl.set_margin_start(16)
        self.ecoin_sub_lbl.set_margin_bottom(14)
        card.pack_start(self.ecoin_sub_lbl, False, False, 0)

        box.pack_start(card, False, False, 0)

        # — Botones fila 1 —
        r1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
        r1.set_halign(Gtk.Align.CENTER)
        r1.set_margin_top(10)
        self.btn_e_install = self._btn("⬇  INSTALAR",      self._on_ecoin_install, primary=True)
        self.btn_e_gui     = self._btn("◈  ABRIR GUI",     self._on_ecoin_gui)
        r1.pack_start(self.btn_e_install, False, False, 0)
        r1.pack_start(self.btn_e_gui,     False, False, 0)
        box.pack_start(r1, False, False, 0)

        # — Botones fila 2 —
        r2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
        r2.set_halign(Gtk.Align.CENTER)
        r2.set_margin_bottom(6)
        self.btn_e_wallet  = self._btn("✦  CREAR WALLET",  self._on_ecoin_wallet)
        self.btn_e_connect = self._btn("⟳  CONECTAR",     self._on_ecoin_connect)
        r2.pack_start(self.btn_e_wallet,  False, False, 0)
        r2.pack_start(self.btn_e_connect, False, False, 0)
        box.pack_start(r2, False, False, 0)

        # — Separator + info —
        sep = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)
        sep.set_margin_top(10)
        box.pack_start(sep, False, False, 0)

        grid = Gtk.Grid()
        grid.set_column_spacing(14)
        grid.set_row_spacing(6)
        grid.set_margin_start(18)
        grid.set_margin_end(18)
        grid.set_margin_top(10)
        grid.set_margin_bottom(14)
        self.ecoin_wallet_val = self._info_row(grid, 0, "Wallet")
        self.ecoin_qt_val     = self._info_row(grid, 1, "ecoin-qt")
        self.ecoin_daemon_val = self._info_row(grid, 2, "ecoind")
        box.pack_start(grid, False, False, 0)

        return scroll

    # ── Tab SISTEMA ───────────────────────────────────────────────────────
    def _make_sistema_tab(self):
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)

        hdr = Gtk.Label(label="Log de actividad")
        hdr.get_style_context().add_class("info-key")
        hdr.set_halign(Gtk.Align.START)
        hdr.set_margin_start(18)
        hdr.set_margin_top(12)
        hdr.set_margin_bottom(4)
        box.pack_start(hdr, False, False, 0)

        scroll = Gtk.ScrolledWindow()
        scroll.get_style_context().add_class("log-scroll")
        scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        scroll.set_vexpand(True)

        self.log_view = Gtk.TextView()
        self.log_view.get_style_context().add_class("log-view")
        self.log_view.set_editable(False)
        self.log_view.set_cursor_visible(False)
        self.log_view.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)
        self.log_buf  = self.log_view.get_buffer()
        self._end_mark = self.log_buf.create_mark(
            "end", self.log_buf.get_end_iter(), False
        )
        scroll.add(self.log_view)
        box.pack_start(scroll, True, True, 0)

        btn = self._btn("LIMPIAR LOG", self._on_clear_log)
        btn.set_margin_start(18)
        btn.set_margin_end(18)
        btn.set_margin_top(8)
        btn.set_margin_bottom(12)
        btn.set_halign(Gtk.Align.CENTER)
        box.pack_start(btn, False, False, 0)

        return box

    # ── Widget helpers ────────────────────────────────────────────────────
    def _btn(self, label, cb, primary=False):
        b = Gtk.Button(label=label)
        b.get_style_context().add_class("btn-primary" if primary else "btn")
        b.connect("clicked", cb)
        return b

    def _info_row(self, grid, row, key):
        k = Gtk.Label(label=key)
        k.get_style_context().add_class("info-key")
        k.set_halign(Gtk.Align.START)
        grid.attach(k, 0, row, 1, 1)

        v = Gtk.Label(label="—")
        v.get_style_context().add_class("info-val")
        v.set_halign(Gtk.Align.START)
        grid.attach(v, 1, row, 1, 1)
        return v

    # ── Dot / state helpers ───────────────────────────────────────────────
    def _dot(self, lbl, state):
        for c in ("dot-running", "dot-stopped", "dot-unknown"):
            lbl.get_style_context().remove_class(c)
        lbl.get_style_context().add_class(f"dot-{state}")

    def _state(self, lbl, text, state):
        for c in ("state-running", "state-stopped", "state-unknown"):
            lbl.get_style_context().remove_class(c)
        lbl.set_text(text)
        lbl.get_style_context().add_class(f"state-{state}")

    # ── Status polling ────────────────────────────────────────────────────
    def _trigger_poll(self):
        threading.Thread(target=self._poll_thread, daemon=True).start()
        return True

    def _poll_thread(self):
        o_inst  = oasis_installed()
        o_run   = oasis_running()
        o_ver   = oasis_version()
        o_node  = node_version()
        e_inst  = ecoin_installed()
        e_wall  = ecoin_wallet_exists()
        e_qt    = (ECOIN_DIR / "ecoin" / "ecoin-qt").is_file()
        e_dmn   = (ECOIN_DIR / "ecoin" / "src" / "ecoind").is_file()
        GLib.idle_add(self._apply_status,
                      o_inst, o_run, o_ver, o_node,
                      e_inst, e_wall, e_qt, e_dmn)

    def _apply_status(self, o_inst, o_run, o_ver, o_node,
                      e_inst, e_wall, e_qt, e_dmn):
        # — Header dot —
        hstate = "running" if o_run else ("stopped" if o_inst else "unknown")
        self._dot(self.header_dot, hstate)

        # — OASIS —
        self._dot(self.oasis_dot, "running" if o_run else ("stopped" if o_inst else "unknown"))
        if o_run:
            self._state(self.oasis_state_lbl, "ACTIVO",       "running")
            self.oasis_sub_lbl.set_text("servidor en puerto 3000")
            self.btn_o_start.set_sensitive(False)
            self.btn_o_stop.set_sensitive(True)
            self.btn_o_browser.set_sensitive(True)
        elif o_inst:
            self._state(self.oasis_state_lbl, "INSTALADO",    "stopped")
            self.oasis_sub_lbl.set_text("servidor detenido")
            self.btn_o_start.set_sensitive(True)
            self.btn_o_stop.set_sensitive(False)
            self.btn_o_browser.set_sensitive(False)
        else:
            self._state(self.oasis_state_lbl, "NO INSTALADO", "unknown")
            self.oasis_sub_lbl.set_text("instala OASIS para comenzar")
            self.btn_o_start.set_sensitive(False)
            self.btn_o_stop.set_sensitive(False)
            self.btn_o_browser.set_sensitive(False)

        self.oasis_ver_val.set_text(f"v{o_ver}" if o_ver != "—" else "—")
        self.oasis_node_val.set_text(o_node if o_node != "—" else "no instalado")
        self.oasis_dir_val.set_text(str(OASIS_DIR) if o_inst else "—")

        # — ECOIN —
        self._dot(self.ecoin_dot, "running" if e_qt else ("stopped" if e_inst else "unknown"))
        if e_inst:
            self._state(self.ecoin_state_lbl, "COMPILADO",    "running")
            self.ecoin_sub_lbl.set_text("wallet ECOIN disponible")
            self.btn_e_gui.set_sensitive(True)
            self.btn_e_wallet.set_sensitive(True)
            self.btn_e_connect.set_sensitive(True)
        else:
            self._state(self.ecoin_state_lbl, "NO INSTALADO", "unknown")
            self.ecoin_sub_lbl.set_text("instala ECOIN para comenzar")
            self.btn_e_gui.set_sensitive(False)
            self.btn_e_wallet.set_sensitive(False)
            self.btn_e_connect.set_sensitive(False)

        self.ecoin_wallet_val.set_text("Sí" if e_wall else "No")
        self.ecoin_qt_val.set_text("Sí" if e_qt else "No")
        self.ecoin_daemon_val.set_text("Sí" if e_dmn else "No")

    # ── Acciones ─────────────────────────────────────────────────────────
    def _run(self, *args):
        """Lanza installer.sh con args, redirige output al log."""
        cmd = [str(INSTALLER_SH)] + list(args)
        self._log(f"$ {' '.join(cmd)}")
        threading.Thread(target=self._exec_log, args=(cmd,), daemon=True).start()
        GLib.idle_add(self.notebook.set_current_page, 2)

    def _exec_log(self, cmd):
        try:
            proc = subprocess.Popen(
                cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                text=True, bufsize=1
            )
            for line in iter(proc.stdout.readline, ""):
                GLib.idle_add(self._log, line.rstrip())
            proc.wait()
            GLib.idle_add(self._log, f"── proceso terminado (código {proc.returncode}) ──")
        except Exception as e:
            GLib.idle_add(self._log, f"[Error]: {e}")
        GLib.idle_add(self._trigger_poll)

    def _log(self, text):
        self.log_buf.insert(self.log_buf.get_end_iter(), text + "\n")
        self.log_view.scroll_to_mark(self._end_mark, 0.0, True, 0.0, 1.0)
        return False

    # OASIS
    def _on_oasis_start(self,   _): self._run("--oasis-start")
    def _on_oasis_install(self, _): self._run("--oasis-install")

    def _on_oasis_stop(self, _):
        self._log("Deteniendo OASIS…")
        threading.Thread(target=self._kill_oasis, daemon=True).start()

    def _kill_oasis(self):
        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 al detener]: {e}")
        GLib.idle_add(self._trigger_poll)

    def _on_oasis_browser(self, _):
        subprocess.Popen(["xdg-open", "http://localhost:3000"])
        self._log("Abriendo http://localhost:3000 …")

    # ECOIN
    def _on_ecoin_install(self, _): self._run("--ecoin-install")
    def _on_ecoin_gui(self,     _): self._run("--ecoin-gui")
    def _on_ecoin_wallet(self,  _): self._run("--wallet-create")
    def _on_ecoin_connect(self, _): self._run("--wallet-connect")

    # Sistema
    def _on_clear_log(self, _):
        self.log_buf.set_text("")


# ── Aplicación ─────────────────────────────────────────────────────────────
class OasisApp(Gtk.Application):
    def __init__(self):
        super().__init__(application_id="net.solarnethub.panel")

    def do_activate(self):
        win = OasisPanel(self)
        win.show_all()


def main():
    # Cargar CSS
    provider = Gtk.CssProvider()
    provider.load_from_data(CSS.encode("utf-8"))
    Gtk.StyleContext.add_provider_for_screen(
        Gdk.Screen.get_default(),
        provider,
        Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
    )

    app = OasisApp()
    sys.exit(app.run(sys.argv))


if __name__ == "__main__":
    main()
