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

Qt6 i QML preko PyQt6. QML nosi cijeli izgled, a ovaj dio samo cita i pise
config, pali i gasi agenta i javlja stanje. Ako se kasnije prijede na cxx-qt,
QML ostaje isti.
"""
from __future__ import annotations

import json
import os
import shutil
import signal
import subprocess
import sys
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

CONFIG = Path.home() / ".config" / "filetoss" / "agent.json"
STANJE = Path.home() / ".config" / "filetoss" / "stanje.json"
NAREDBA = Path.home() / ".config" / "filetoss" / "naredba.json"
AUTOSTART = Path.home() / ".config" / "autostart" / "filetoss-tray.desktop"
APP_URL = "https://filetoss.humanitics.eu/app/"
SCREENTOSS_URL = "https://screentoss.humanitics.eu/"

# Prvo instalirani binarij, pa lokalni build za razvoj.
KANDIDATI = [
    Path("/usr/bin/filetoss"),
    Path(__file__).resolve().parent.parent / "agent-rs" / "target" / "release" / "filetoss",
]


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


def zadana_mapa() -> str:
    return str(Path.home() / "Downloads" / "Filetoss")


def otvori_lokalno_ili_online(nazivi: list[str], url: str) -> str:
    """Pokreni lokalnu aplikaciju, a na mrezu idi tek ako je nema.

    Web verzija je zamjena, ne odrediste: ako je program instaliran, korisnik
    ocekuje njega, s pristupom datotekama i bez prijave u pregledniku.
    """
    for naziv in nazivi:
        put = shutil.which(naziv)
        if put:
            subprocess.Popen([put], start_new_session=True)
            return f"Otvaram {naziv}."
    # Zadnja postaja: web.
    subprocess.Popen(["xdg-open", url], start_new_session=True)
    return "Aplikacija nije instalirana, otvaram web."


class Agent(QObject):
    promjena = pyqtSignal()

    def __init__(self) -> None:
        super().__init__()
        self._cfg = self._ucitaj()
        self._poruka = "Spremno."
        self._greska = False
        self._zauzet = False
        self._pid: int | None = None
        self._stanje: dict = {}
        self._nadzor = QTimer(self)
        self._nadzor.timeout.connect(self._osvjezi)
        self._nadzor.start(1500)
        self._osvjezi()

    # ---------- config ----------

    def _ucitaj(self) -> dict:
        try:
            return json.loads(CONFIG.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)

    # ---------- stanje ----------

    def _nadi_pid(self) -> int | None:
        try:
            out = subprocess.run(
                ["pgrep", "-x", "filetoss"], capture_output=True, text=True, timeout=2
            )
            prvi = out.stdout.split()
            return int(prvi[0]) if prvi else None
        except Exception:
            return None

    def _ucitaj_stanje(self) -> dict:
        try:
            s = json.loads(STANJE.read_text())
        except Exception:
            return {}
        # Ustajalo stanje znaci da agent vise ne radi.
        import time

        if time.time() - float(s.get("azurirano", 0)) > 8:
            return {}
        return s

    def _osvjezi(self) -> None:
        stari = (self._pid, self._cfg.get("user"), json.dumps(self._stanje, sort_keys=True))
        self._cfg = self._ucitaj() or self._cfg
        self._pid = self._nadi_pid()
        self._stanje = self._ucitaj_stanje()
        novi = (self._pid, self._cfg.get("user"), json.dumps(self._stanje, sort_keys=True))
        if novi != stari:
            self.promjena.emit()

    # ---------- svojstva za QML ----------

    @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 mapa(self) -> str:
        return self._cfg.get("downloadDir") or zadana_mapa()

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

    @pyqtProperty(bool, notify=promjena)
    def autostart(self) -> bool:
        return AUTOSTART.exists()

    @pyqtProperty(bool, notify=promjena)
    def radi(self) -> bool:
        return self._pid is not None

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

    @pyqtProperty(list, notify=promjena)
    def uredaji(self) -> list:
        return [
            {"uid": p.get("uid", ""), "name": p.get("name", "uređaj"), "via": p.get("via", "")}
            for p in self._stanje.get("peers", [])
        ]

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

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

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

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

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

    @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", os.uname().nodename)
        self._cfg.setdefault("autoAccept", True)
        self._spremi()
        self._zauzet = True
        self._javi(f"Prijavljujem {user}…")
        self._pokreni(lozinka)
        QTimer.singleShot(3000, self._provjeri_prijavu)

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

    def _pokreni(self, lozinka: str | None = None) -> None:
        bin_ = binarij()
        if not bin_:
            self._javi("Ne nalazim filetoss binarij.", True)
            return
        okolina = dict(os.environ)
        if lozinka:
            okolina["FILETOSS_PASS"] = lozinka
        try:
            subprocess.Popen(
                [str(bin_)],
                env=okolina,
                stdout=subprocess.DEVNULL,
                stderr=subprocess.DEVNULL,
                start_new_session=True,
            )
        except Exception as e:  # pragma: no cover
            self._javi(f"Pokretanje nije uspjelo: {e}", True)

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

    @pyqtSlot()
    def zaustavi(self) -> None:
        if self._pid:
            try:
                os.kill(self._pid, signal.SIGTERM)
            except Exception:
                pass
        self._javi("Zaustavljeno.")
        QTimer.singleShot(800, self._osvjezi)

    @pyqtSlot()
    def odjavi(self) -> None:
        self.zaustavi()
        for k in ("token", "user"):
            self._cfg.pop(k, None)
        self._spremi()
        self._javi("Odjavljen.")

    @pyqtSlot(str)
    def postaviIme(self, ime: str) -> None:
        ime = ime.strip()[:48]
        if ime and ime != self._cfg.get("name"):
            self._cfg["name"] = ime
            self._spremi()
            self._javi("Naziv spremljen, vrijedi od sljedećeg pokretanja.")

    @pyqtSlot(str)
    def postaviMapu(self, url: str) -> None:
        put = QUrl(url).toLocalFile() if url.startswith("file:") else url
        if put:
            self._cfg["downloadDir"] = put
            self._spremi()
            self._javi(f"Sync mapa: {put}")

    @pyqtSlot(bool)
    def postaviAutoAccept(self, v: bool) -> None:
        self._cfg["autoAccept"] = bool(v)
        self._spremi()
        # Agent to primi odmah, bez ponovnog pokretanja.
        self._naredi({"t": "auto-accept", "enabled": bool(v)})
        self._javi("Primam bez pitanja." if v else "Svaki prijenos tražit će potvrdu.")

    @pyqtSlot(bool)
    def postaviAutostart(self, v: bool) -> None:
        if v:
            AUTOSTART.parent.mkdir(parents=True, exist_ok=True)
            izvor = Path("/etc/xdg/autostart/filetoss-tray.desktop")
            if izvor.exists():
                AUTOSTART.write_text(izvor.read_text())
            else:
                AUTOSTART.write_text(
                    "[Desktop Entry]\nType=Application\nName=Filetoss\n"
                    f"Exec={binarij() or 'filetoss'}\nX-GNOME-Autostart-enabled=true\n"
                )
            self._javi("Filetoss se diže s prijavom.")
        else:
            AUTOSTART.unlink(missing_ok=True)
            self._javi("Automatsko pokretanje isključeno.")
        self.promjena.emit()

    @pyqtSlot()
    def otvoriMapu(self) -> None:
        Path(self.mapa).mkdir(parents=True, exist_ok=True)
        subprocess.Popen(["xdg-open", self.mapa], start_new_session=True)

    def _naredi(self, naredba: dict) -> bool:
        if not self.radi:
            self._javi("Filetoss nije pokrenut.", True)
            return False
        try:
            NAREDBA.parent.mkdir(parents=True, exist_ok=True)
            NAREDBA.write_text(json.dumps(naredba))
            return True
        except Exception as e:
            self._javi(f"Naredba nije zapisana: {e}", True)
            return False

    @pyqtSlot()
    def objasniPrazno(self) -> None:
        """Gumb ne smije biti mrtav; ako nema kome slati, reci zasto."""
        if not self._stanje:
            self._javi(
                "Agent ne javlja stanje. Vjerojatno je stara verzija; "
                "zaustavi ga i pokreni ponovo.",
                True,
            )
        else:
            self._javi(
                "Nema vidljivih uređaja. Otvori Filetoss na drugom uređaju, "
                "s istim računom.",
                True,
            )

    @pyqtSlot(str, list)
    def posalji(self, peer_uid: str, putanje: list) -> None:
        """Posalji lokalno preko agenta, bez preglednika."""
        datoteke = []
        for u in putanje:
            put = QUrl(u).toLocalFile() if str(u).startswith("file:") else str(u)
            if put:
                datoteke.append(put)
        if not datoteke:
            return
        if not peer_uid:
            self._javi("Odaberi uređaj kojem šalješ.", True)
            return
        if self._naredi({"t": "send", "peerUid": peer_uid, "paths": datoteke}):
            koliko = len(datoteke)
            self._javi(f"Šaljem {koliko} {'datoteku' if koliko == 1 else 'datoteke'}…")

    @pyqtSlot()
    def otvoriApp(self) -> None:
        subprocess.Popen(["xdg-open", APP_URL], start_new_session=True)

    @pyqtSlot()
    def otvoriScreentoss(self) -> None:
        self._javi(
            otvori_lokalno_ili_online(
                ["screentoss-gui", "screentoss-tray", "screentoss"], SCREENTOSS_URL
            )
        )


def main() -> int:
    # Basic stil, jer sistemski (org.kde.desktop) vuce svijetlu paletu i onda
    # polja i prekidaci izgledaju kao tuda zakrpa na tamnoj kartici.
    os.environ["QT_QUICK_CONTROLS_STYLE"] = "Basic"
    app = QGuiApplication(sys.argv)
    app.setApplicationName("Filetoss")
    app.setDesktopFileName("filetoss")
    ikona = Path(__file__).resolve().parent / "qml" / "logo.svg"
    if ikona.exists():
        app.setWindowIcon(QIcon(str(ikona)))

    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())
