#!/usr/bin/env python3
"""Screentoss kontrolni prozor.

Isti pristup kao kod Filetossa: QML nosi izgled, Python cita config i status
koji agent vec pise, pali i gasi agenta i pokrece provjeru sustava.
"""
from __future__ import annotations

import json
import os
import shutil
import signal
import subprocess
import sys
import time
from pathlib import Path

from PyQt6.QtCore import QObject, QTimer, QUrl, pyqtProperty, pyqtSignal, pyqtSlot
from PyQt6.QtGui import QGuiApplication, QIcon
from PyQt6.QtQml import QQmlApplicationEngine

sys.path.insert(0, str(Path(__file__).resolve().parent))
from jezik import prevedi  # noqa: E402

CONFIG = Path.home() / ".config" / "screentoss" / "agent.json"
STATUS = Path.home() / ".config" / "screentoss" / "status.json"
AUTOSTART = Path.home() / ".config" / "autostart" / "screentoss-tray.desktop"
POSTAVKE = Path.home() / ".config" / "screentoss" / "prozor.json"
VIEWER_URL = "https://screentoss.humanitics.eu/app/"
FILETOSS_URL = "https://filetoss.humanitics.eu/app/"

KANDIDATI = [
    Path("/usr/bin/screentoss"),
    Path(__file__).resolve().parent.parent / "agent-rs" / "target" / "release" / "screentoss",
]


def binarij() -> Path | None:
    for p in KANDIDATI:
        if p.exists() and os.access(p, os.X_OK):
            return p
    return None


def otvori_lokalno_ili_online(nazivi: list[str], url: str) -> str:
    """Lokalna aplikacija ima prednost; web je zamjena, ne odrediste."""
    for naziv in nazivi:
        put = shutil.which(naziv)
        if put:
            subprocess.Popen([put], start_new_session=True)
            return f"Otvaram {naziv}."
    subprocess.Popen(["xdg-open", url], start_new_session=True)
    return "Aplikacija nije instalirana, otvaram web."


def formatiraj_id(sirovi: str) -> str:
    cist = "".join(c for c in sirovi if c in "0123456789abcdefABCDEF").upper()
    if not cist:
        return "---"
    return " ".join(cist[i : i + 4] for i in range(0, len(cist), 4))


class Agent(QObject):
    promjena = pyqtSignal()

    def __init__(self) -> None:
        super().__init__()
        self._cfg: dict = {}
        self._status: dict = {}
        self._poruka = "Spremno."
        self._greska = False
        self._zauzet = False
        self._provjera_tece = False
        self._izvjestaj = ""
        self._postavke = self._json(POSTAVKE)
        self._jezik = self._postavke.get("jezik") or ("en" if not str(
            os.environ.get("LANG", "")).lower().startswith("hr") else "hr")
        self._nadzor = QTimer(self)
        self._nadzor.timeout.connect(self._osvjezi)
        self._nadzor.start(1500)
        self._osvjezi()

    # ---------- citanje ----------

    def _json(self, put: Path) -> dict:
        try:
            return json.loads(put.read_text())
        except Exception:
            return {}

    def _spremi(self) -> None:
        CONFIG.parent.mkdir(parents=True, exist_ok=True)
        CONFIG.write_text(json.dumps(self._cfg, indent=2))
        CONFIG.chmod(0o600)

    def _osvjezi(self) -> None:
        prije = (json.dumps(self._cfg, sort_keys=True), json.dumps(self._status, sort_keys=True))
        self._cfg = self._json(CONFIG) or self._cfg
        st = self._json(STATUS)
        # Sat nije dokaz: agent status pise pri promjeni faze. Pravi dokaz je
        # zivi proces s tim pidom.
        pid = int(st.get("pid") or 0)
        ziv = pid > 0 and Path(f"/proc/{pid}").exists()
        ustajalo = time.time() - float(st.get("updatedAt", 0)) > 60
        if st and not ziv and ustajalo:
            st = {**st, "online": False, "phase": "offline"}
        self._status = st
        poslije = (json.dumps(self._cfg, sort_keys=True), json.dumps(self._status, sort_keys=True))
        if prije != poslije:
            self.promjena.emit()

    def _spremi_postavke(self) -> None:
        POSTAVKE.parent.mkdir(parents=True, exist_ok=True)
        POSTAVKE.write_text(json.dumps(self._postavke, indent=2))

    # ---------- jezik ----------

    @pyqtSlot(str, result=str)
    def t(self, kljuc: str) -> str:
        return prevedi(kljuc, self._jezik)

    @pyqtProperty(str, notify=promjena)
    def jezik(self) -> str:
        return self._jezik

    @pyqtSlot()
    def promijeniJezik(self) -> None:
        self._jezik = "en" if self._jezik == "hr" else "hr"
        self._postavke["jezik"] = self._jezik
        self._spremi_postavke()
        self.promjena.emit()

    # ---------- povijest spajanja ----------

    @pyqtProperty(list, notify=promjena)
    def povijest(self) -> list:
        return list(self._postavke.get("povijest", []))

    @pyqtSlot(str, str)
    def zapamtiHost(self, uid: str, naziv: str) -> None:
        if not uid:
            return
        stavke = [x for x in self._postavke.get("povijest", []) if x.get("uid") != uid]
        stavke.insert(0, {"uid": uid, "naziv": naziv or uid, "kada": int(time.time())})
        self._postavke["povijest"] = stavke[:8]
        self._spremi_postavke()
        self.promjena.emit()

    @pyqtSlot(str)
    def zaboraviHost(self, uid: str) -> None:
        self._postavke["povijest"] = [
            x for x in self._postavke.get("povijest", []) if x.get("uid") != uid
        ]
        self._spremi_postavke()
        self.promjena.emit()

    # ---------- svojstva ----------

    @pyqtProperty(bool, notify=promjena)
    def prijavljen(self) -> bool:
        return bool(self._cfg.get("token"))

    @pyqtProperty(str, notify=promjena)
    def user(self) -> str:
        return self._cfg.get("user") or ""

    @pyqtProperty(str, notify=promjena)
    def ime(self) -> str:
        return self._cfg.get("name") or ""

    @pyqtProperty(str, notify=promjena)
    def deviceId(self) -> str:
        return formatiraj_id(self._cfg.get("deviceId") or "")

    @pyqtProperty(bool, notify=promjena)
    def unattended(self) -> bool:
        return bool(self._cfg.get("unattended"))

    @pyqtProperty(bool, notify=promjena)
    def portalPriPokretanju(self) -> bool:
        return bool(self._cfg.get("portalPriPokretanju", True))

    @pyqtProperty(bool, notify=promjena)
    def online(self) -> bool:
        return bool(self._status.get("online"))

    @pyqtProperty(bool, notify=promjena)
    def radi(self) -> bool:
        faza = self._status.get("phase", "")
        return bool(self._status.get("online")) or faza in {
            "connecting",
            "auth",
            "register",
            "starting",
        }

    @pyqtProperty(str, notify=promjena)
    def phase(self) -> str:
        return self._status.get("phase", "offline")

    @pyqtProperty(bool, notify=promjena)
    def zauzet(self) -> bool:
        return self._zauzet

    @pyqtProperty(bool, notify=promjena)
    def provjeraTece(self) -> bool:
        return self._provjera_tece

    @pyqtProperty(str, notify=promjena)
    def izvjestaj(self) -> str:
        return self._izvjestaj

    @pyqtProperty(str, notify=promjena)
    def sazetakSustava(self) -> str:
        sesija = os.environ.get("XDG_SESSION_TYPE", "?")
        if sesija == "wayland":
            return "Wayland: slika i kontrola idu preko portala za dijeljenje ekrana."
        return "X11: slika ide izravno, kontrola preko xdotoola."

    @pyqtProperty(str, notify=promjena)
    def poruka(self) -> str:
        return self._poruka

    def _javi(self, tekst: str, greska: bool = False) -> None:
        self._poruka = tekst
        self._greska = greska
        self.promjena.emit()

    # ---------- radnje ----------

    def _pokreni(self, lozinka: str | None = None) -> None:
        bin_ = binarij()
        if not bin_:
            self._javi("Ne nalazim screentoss binarij.", True)
            return
        okolina = dict(os.environ)
        okolina["SCREENTOSS_USER"] = self._cfg.get("user", "")
        if lozinka:
            okolina["SCREENTOSS_PASS"] = lozinka
        # Tray je vlasnik agenta. Ako prozor pokrene jos jednog, na racunu se
        # pojave dva ista uredaja i slanje zavrsi kod samog sebe.
        tray_put = shutil.which("screentoss-tray") or str(bin_)
        subprocess.Popen(
            [tray_put, "--autostart"] if tray_put.endswith("screentoss-tray") else [str(bin_)],
            env=okolina,
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
            start_new_session=True,
        )

    @pyqtSlot(str, str)
    def prijavi(self, user: str, lozinka: str) -> None:
        user = user.strip().lower()
        if len(user) < 3 or len(lozinka) < 4:
            self._javi("Korisnik i lozinka su prekratki.", True)
            return
        self._cfg["user"] = user
        self._cfg.setdefault("name", f"ubuntu-{os.uname().nodename}"[:40])
        self._cfg.setdefault("signal", "wss://screentoss.humanitics.eu/ws")
        self._spremi()
        self._zauzet = True
        self._javi(f"Prijavljujem {user}…")
        self._pokreni(lozinka)
        QTimer.singleShot(4000, self._provjeri_prijavu)

    def _provjeri_prijavu(self) -> None:
        self._zauzet = False
        self._cfg = self._json(CONFIG)
        if self._cfg.get("token"):
            self._javi("Prijavljen.")
        else:
            self._javi("Prijava nije uspjela. Provjeri lozinku.", True)
        self.promjena.emit()

    @pyqtSlot()
    def pokreni(self) -> None:
        self._pokreni()
        self._javi("Pokrećem…")
        QTimer.singleShot(1500, self._osvjezi)

    @pyqtSlot()
    def zaustavi(self) -> None:
        pid = int(self._status.get("pid") or 0)
        if pid:
            try:
                os.kill(pid, signal.SIGTERM)
            except Exception:
                pass
        for naziv in ("screentoss-tray", "screentoss"):
            subprocess.run(["pkill", "-x", naziv], capture_output=True)
        self._javi("Zaustavljeno.")
        QTimer.singleShot(1000, self._osvjezi)

    @pyqtSlot()
    def odjavi(self) -> None:
        self.zaustavi()
        for k in ("token", "deviceId", "deviceSecret"):
            self._cfg.pop(k, None)
        self._spremi()
        self._javi("Odjavljen. Uređaj će se registrirati ponovo.")

    @pyqtSlot(bool)
    def postaviNadzor(self, v: bool) -> None:
        self._cfg["unattended"] = bool(v)
        self._spremi()
        self._javi(
            "Spajanje bez potvrde uključeno."
            if v
            else "Svaku sesiju potvrđuješ dijalogom."
        )
        if self.radi:
            self._javi("Promjena vrijedi nakon ponovnog pokretanja agenta.")

    @pyqtSlot(bool)
    def postaviPortal(self, v: bool) -> None:
        self._cfg["portalPriPokretanju"] = bool(v)
        self._spremi()
        self._javi(
            "Dijeljenje se priprema pri pokretanju."
            if v
            else "Dijeljenje se traži tek pri prvoj vezi."
        )

    @pyqtSlot()
    def provjeriSustav(self) -> None:
        bin_ = binarij()
        if not bin_:
            self._javi("Ne nalazim screentoss binarij.", True)
            return
        self._provjera_tece = True
        self._izvjestaj = ""
        self._javi("Provjeravam sustav, potvrdi dijalog ako ga bude…")

        def gotovo() -> None:
            try:
                out = subprocess.run(
                    [str(bin_), "--provjera"],
                    capture_output=True,
                    text=True,
                    timeout=150,
                )
                self._izvjestaj = (out.stdout or out.stderr).strip()
                self._javi("Provjera gotova.")
            except Exception as e:
                self._izvjestaj = str(e)
                self._javi("Provjera nije uspjela.", True)
            self._provjera_tece = False
            self.promjena.emit()

        QTimer.singleShot(50, gotovo)

    @pyqtSlot()
    def otvoriViewer(self) -> None:
        self.spojiSe("", "")

    @pyqtSlot(str, str)
    def spojiSe(self, uid: str, naziv: str) -> None:
        """Otvori viewer u vlastitom prozoru, bez kartica i adresne trake.

        Pravi native viewer znaci vlastitu WebRTC implementaciju s H.264
        dekoderom, dakle drugi proizvod. Ovo je prozor aplikacije, ne kartica u
        pregledniku: bez adresne trake, s vlastitom ikonom i historijom ovdje.
        """
        url = VIEWER_URL + (f"?host={uid}" if uid else "")
        for prog in ("google-chrome", "chromium", "chromium-browser", "brave-browser"):
            put = shutil.which(prog)
            if put:
                subprocess.Popen(
                    [put, f"--app={url}", "--class=Screentoss-Viewer"],
                    start_new_session=True,
                )
                if uid:
                    self.zapamtiHost(uid, naziv)
                self._javi(self.t("otvoriViewer"))
                return
        subprocess.Popen(["xdg-open", url], start_new_session=True)

    @pyqtSlot()
    def otvoriFiletoss(self) -> None:
        self._javi(
            otvori_lokalno_ili_online(["filetoss-gui", "filetoss-tray", "filetoss"], FILETOSS_URL)
        )


def osiguraj_tray(naziv: str) -> None:
    """Podigni tray ako ne radi.

    Prozor i tray su zasebni procesi: zatvaranje prozora ne smije ostaviti
    korisnika bez ikone u traci, jer bi tada agent radio, a nista to ne bi
    pokazivalo.
    """
    try:
        vec = subprocess.run(["pgrep", "-x", naziv], capture_output=True, timeout=2)
        if vec.returncode == 0:
            return
    except Exception:
        return
    put = shutil.which(naziv)
    if put:
        # --autostart znaci samo ikona u traci, bez vlastitog prozora; inace bi
        # se uz ovaj prozor otvorio i stari tray prozor.
        subprocess.Popen(
            [put, "--autostart"],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
            start_new_session=True,
        )


def main() -> int:
    os.environ["QT_QUICK_CONTROLS_STYLE"] = "Basic"
    app = QGuiApplication(sys.argv)
    app.setApplicationName("Screentoss")
    app.setDesktopFileName("screentoss")
    ikona = Path(__file__).resolve().parent / "qml" / "logo.svg"
    if ikona.exists():
        app.setWindowIcon(QIcon(str(ikona)))

    osiguraj_tray("screentoss-tray")

    engine = QQmlApplicationEngine()
    agent = Agent()
    engine.rootContext().setContextProperty("agent", agent)
    qml = Path(__file__).resolve().parent / "qml" / "main.qml"
    engine.load(QUrl.fromLocalFile(str(qml)))
    if not engine.rootObjects():
        print("QML se nije ucitao", file=sys.stderr)
        return 1
    return app.exec()


if __name__ == "__main__":
    raise SystemExit(main())
