"""PC Central - aplikasi kontrol di komputer pusat. Tanpa login (memakai admin_key di central_config.json)."""
import json
import os
import queue
import sys
import threading
import time
import tkinter as tk
from tkinter import messagebox, simpledialog, ttk

from common import Api, ApiError

REFRESH_MS = 3000


def base_dir():
    if getattr(sys, "frozen", False):
        return os.path.dirname(sys.executable)
    return os.path.dirname(os.path.abspath(__file__))


CONFIG_PATH = os.path.join(base_dir(), "central_config.json")


def load_config():
    if os.path.exists(CONFIG_PATH):
        with open(CONFIG_PATH, "r", encoding="utf-8") as fh:
            return json.load(fh)
    return {}


def save_config(cfg):
    with open(CONFIG_PATH, "w", encoding="utf-8") as fh:
        json.dump(cfg, fh, indent=2)


def fmt_time(ts):
    return time.strftime("%H:%M:%S", time.localtime(ts)) if ts else "-"


class CentralApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("PC Central - Monitoring & Kontrol")
        self.geometry("1180x700")
        self.minsize(980, 560)
        self.cfg = load_config()
        self.api = None
        self.results = queue.Queue()
        self.devices = {}
        self.selected_id = None
        self.all_var = tk.BooleanVar(value=False)
        self.busy = False

        self._build_ui()
        if not self.cfg.get("server_url") or not self.cfg.get("admin_key"):
            self.after(200, self.open_settings)
        else:
            self._make_api()
        self.after(100, self._poll_results)
        self.after(300, self.refresh)

    # ------------------------------------------------------------------ UI
    def _build_ui(self):
        style = ttk.Style(self)
        try:
            style.theme_use("clam")
        except tk.TclError:
            pass
        style.configure("Treeview", rowheight=24)

        bar = ttk.Frame(self, padding=(8, 8, 8, 4))
        bar.pack(fill="x")
        self.buttons = []
        for text, cmd in [
            ("\U0001F512 Kunci layar", lambda: self.act(locked=True, ask_msg=True)),
            ("\U0001F513 Buka kunci", lambda: self.act(locked=False)),
            ("⛔ Blokir internet", lambda: self.act(internet_blocked=True)),
            ("✅ Buka internet", lambda: self.act(internet_blocked=False)),
        ]:
            b = ttk.Button(bar, text=text, command=cmd)
            b.pack(side="left", padx=2)
            self.buttons.append(b)
        ttk.Separator(bar, orient="vertical").pack(side="left", fill="y", padx=8)
        ttk.Label(bar, text="Maks. tab Chrome:").pack(side="left")
        self.tabs_var = tk.IntVar(value=0)
        ttk.Spinbox(bar, from_=0, to=500, width=5, textvariable=self.tabs_var).pack(side="left", padx=4)
        ttk.Button(bar, text="Terapkan", command=lambda: self.act(max_tabs=self._int(self.tabs_var))).pack(side="left")
        ttk.Label(bar, text="(0 = tanpa batas)").pack(side="left", padx=4)
        ttk.Separator(bar, orient="vertical").pack(side="left", fill="y", padx=8)
        ttk.Button(bar, text="Situs terlarang...", command=self.edit_sites).pack(side="left", padx=2)
        ttk.Button(bar, text="Ganti nama", command=self.rename).pack(side="left", padx=2)
        ttk.Button(bar, text="Hapus PC", command=self.delete_device).pack(side="left", padx=2)
        ttk.Button(bar, text="Pengaturan", command=self.open_settings).pack(side="right", padx=2)
        ttk.Checkbutton(bar, text="Terapkan ke SEMUA PC", variable=self.all_var).pack(side="right", padx=10)

        paned = ttk.PanedWindow(self, orient="horizontal")
        paned.pack(fill="both", expand=True, padx=8, pady=4)

        left = ttk.Frame(paned)
        cols = ("name", "status", "lock", "net", "tabs", "app")
        self.tree = ttk.Treeview(left, columns=cols, show="headings", selectmode="browse")
        for col, text, width in [("name", "Nama PC", 130), ("status", "Status", 70), ("lock", "Layar", 70),
                                 ("net", "Internet", 70), ("tabs", "Tab", 45), ("app", "Sedang dibuka", 260)]:
            self.tree.heading(col, text=text)
            self.tree.column(col, width=width, anchor="w")
        self.tree.tag_configure("offline", foreground="#9ca3af")
        self.tree.tag_configure("locked", foreground="#b91c1c")
        self.tree.pack(fill="both", expand=True)
        self.tree.bind("<<TreeviewSelect>>", self._on_select)
        paned.add(left, weight=3)

        right = ttk.Frame(paned)
        self.info_var = tk.StringVar(value="Pilih PC di sebelah kiri.")
        ttk.Label(right, textvariable=self.info_var, font=("Segoe UI", 10, "bold"), wraplength=520).pack(anchor="w", pady=(0, 4))
        nb = ttk.Notebook(right)
        nb.pack(fill="both", expand=True)
        self.lists = {}
        for key, title, cols in [
            ("windows", "Jendela terbuka", [("title", "Judul", 360), ("process", "Aplikasi", 140)]),
            ("tabs", "Tab Chrome", [("title", "Judul", 220), ("url", "URL", 280)]),
            ("processes", "Proses", [("name", "Nama proses", 300), ("count", "Jumlah", 80)]),
            ("history", "Riwayat", [("time", "Jam", 80), ("title", "Jendela", 300), ("process", "Aplikasi", 120)]),
        ]:
            frame = ttk.Frame(nb)
            tv = ttk.Treeview(frame, columns=[c[0] for c in cols], show="headings")
            for cid, ctitle, w in cols:
                tv.heading(cid, text=ctitle)
                tv.column(cid, width=w, anchor="w")
            sb = ttk.Scrollbar(frame, orient="vertical", command=tv.yview)
            tv.configure(yscrollcommand=sb.set)
            tv.pack(side="left", fill="both", expand=True)
            sb.pack(side="right", fill="y")
            nb.add(frame, text=title)
            self.lists[key] = tv
        paned.add(right, weight=4)

        self.status_var = tk.StringVar(value="Belum terhubung")
        ttk.Label(self, textvariable=self.status_var, relief="sunken", anchor="w", padding=4).pack(fill="x", side="bottom")

    # -------------------------------------------------------------- helpers
    @staticmethod
    def _int(var):
        try:
            return max(0, int(var.get()))
        except (tk.TclError, ValueError):
            return 0

    def _make_api(self):
        self.api = Api(self.cfg["server_url"], self.cfg["admin_key"])

    def _bg(self, fn, then=None):
        """Jalankan pemanggilan jaringan di thread lain; hasil diproses di thread GUI."""
        def worker():
            try:
                self.results.put((then, fn(), None))
            except ApiError as exc:
                self.results.put((then, None, exc))
            except Exception as exc:
                self.results.put((then, None, ApiError(repr(exc))))
        threading.Thread(target=worker, daemon=True).start()

    def _poll_results(self):
        try:
            while True:
                then, result, err = self.results.get_nowait()
                if then:
                    then(result, err)
        except queue.Empty:
            pass
        self.after(100, self._poll_results)

    # -------------------------------------------------------------- refresh
    def refresh(self):
        if self.api and not self.busy:
            self.busy = True
            sel = self.selected_id

            def fetch():
                data = {"list": self.api.call("devices")}
                if sel:
                    try:
                        data["detail"] = self.api.call("device", id=sel)
                    except ApiError:
                        data["detail"] = None
                return data

            self._bg(fetch, self._on_refresh)
        self.after(REFRESH_MS, self.refresh)

    def _on_refresh(self, data, err):
        self.busy = False
        if err:
            self.status_var.set("Gagal terhubung: %s" % err)
            return
        devices = data["list"]["devices"]
        self.devices = {d["id"]: d for d in devices}
        online = sum(1 for d in devices if d["online"])
        self.status_var.set("Terhubung - %d PC (%d online) - diperbarui %s" % (len(devices), online, time.strftime("%H:%M:%S")))

        existing = set(self.tree.get_children())
        for d in devices:
            tags = []
            if not d["online"]:
                tags.append("offline")
            if d["locked"]:
                tags.append("locked")
            values = (d["name"], "Online" if d["online"] else "Offline",
                      "TERKUNCI" if d["locked"] else "Terbuka",
                      "DIBLOKIR" if d["internet_blocked"] else "Normal",
                      d["tab_count"], ("%s  [%s]" % (d["active_window"], d["active_process"])) if d["active_window"] else "-")
            if d["id"] in existing:
                self.tree.item(d["id"], values=values, tags=tags)
            else:
                self.tree.insert("", "end", iid=d["id"], values=values, tags=tags)
        for gone in existing - set(self.devices):
            self.tree.delete(gone)
        if self.selected_id and self.selected_id not in self.devices:
            self.selected_id = None
        if data.get("detail"):
            self._show_detail(data["detail"])

    def _on_select(self, _event):
        sel = self.tree.selection()
        self.selected_id = sel[0] if sel else None
        if self.selected_id and self.selected_id in self.devices:
            self.tabs_var.set(self.devices[self.selected_id]["max_tabs"])
            self.busy = False
            self._bg(lambda sid=self.selected_id: self.api.call("device", id=sid),
                     lambda r, e: self._show_detail(r) if r else None)

    def _show_detail(self, data):
        dev = data["device"]
        if dev["id"] != self.selected_id:
            return
        state = "ONLINE" if dev["online"] else "OFFLINE (terakhir %s)" % fmt_time(dev["last_seen"])
        sites = len(dev["blocked_sites"])
        self.info_var.set("%s  |  %s  |  IP %s  |  %s  |  situs terlarang: %d  |  maks tab: %s" % (
            dev["name"], state, dev["ip"], dev["os"], sites, dev["max_tabs"] or "tanpa batas"))

        def fill(key, rows):
            tv = self.lists[key]
            tv.delete(*tv.get_children())
            for row in rows:
                tv.insert("", "end", values=row)

        fill("windows", [(w.get("title", ""), w.get("process", "")) for w in data["windows"]])
        fill("tabs", [(t.get("title", ""), t.get("url", "")) for t in data["tabs"]])
        fill("processes", [(p.get("name", ""), p.get("count", 1)) for p in data["processes"]])
        fill("history", [(fmt_time(h["ts"]), h["active_window"], h["active_process"]) for h in data["history"]])

    # -------------------------------------------------------------- aksi
    def _target(self):
        if self.all_var.get():
            return "*"
        if not self.selected_id:
            messagebox.showinfo("Pilih PC", "Pilih satu PC di daftar, atau centang 'Terapkan ke SEMUA PC'.")
            return None
        return self.selected_id

    def act(self, ask_msg=False, **fields):
        if not self.api:
            return self.open_settings()
        target = self._target()
        if target is None:
            return
        if target == "*" and not messagebox.askyesno("Konfirmasi", "Terapkan ke SEMUA PC?"):
            return
        if ask_msg:
            msg = simpledialog.askstring("Pesan di layar kunci", "Pesan untuk ditampilkan (boleh kosong):", parent=self)
            if msg is None:
                return
            fields["lock_message"] = msg
        payload = dict(fields, device_id=target)
        self.status_var.set("Mengirim perintah...")

        def done(res, err):
            if err:
                messagebox.showerror("Gagal", str(err))
            else:
                self.status_var.set("Perintah terkirim ke %d PC (berlaku dalam beberapa detik)." % res["affected"])
                self.busy = False
        self._bg(lambda: self.api.call("set", payload), done)

    def edit_sites(self):
        if not self.api:
            return self.open_settings()
        target = self._target()
        if target is None:
            return
        current = ""
        if target != "*" and target in self.devices:
            current = "\n".join(self.devices[target]["blocked_sites"])
        win = tk.Toplevel(self)
        win.title("Situs terlarang (satu domain per baris)")
        win.geometry("420x360")
        win.transient(self)
        ttk.Label(win, text="Contoh: youtube.com, facebook.com\nSubdomain ikut terblokir.", padding=8).pack(anchor="w")
        text = tk.Text(win, height=12)
        text.pack(fill="both", expand=True, padx=8)
        text.insert("1.0", current)

        def save():
            sites = text.get("1.0", "end").strip()
            win.destroy()
            self.act(blocked_sites=sites)
        ttk.Button(win, text="Simpan", command=save).pack(pady=8)

    def rename(self):
        if not self.selected_id:
            return messagebox.showinfo("Pilih PC", "Pilih satu PC dulu.")
        new = simpledialog.askstring("Ganti nama", "Nama baru:", initialvalue=self.devices[self.selected_id]["name"], parent=self)
        if new:
            sid = self.selected_id
            self._bg(lambda: self.api.call("set", {"device_id": sid, "name": new}), lambda r, e: setattr(self, "busy", False))

    def delete_device(self):
        if not self.selected_id:
            return messagebox.showinfo("Pilih PC", "Pilih satu PC dulu.")
        d = self.devices[self.selected_id]
        if messagebox.askyesno("Hapus", "Hapus '%s' beserta riwayatnya dari daftar?\n(PC akan muncul lagi bila agent masih aktif.)" % d["name"]):
            sid = self.selected_id
            self._bg(lambda: self.api.call("delete_device", {"device_id": sid}), lambda r, e: setattr(self, "busy", False))

    def open_settings(self):
        url = simpledialog.askstring("Pengaturan", "URL API server\n(contoh: https://domainanda.com/pcmon/api.php):",
                                     initialvalue=self.cfg.get("server_url", ""), parent=self)
        if url is None:
            return
        key = simpledialog.askstring("Pengaturan", "admin_key (dari config.php di server):",
                                     initialvalue=self.cfg.get("admin_key", ""), parent=self, show="*")
        if key is None:
            return
        self.cfg = {"server_url": url.strip(), "admin_key": key.strip()}
        save_config(self.cfg)
        self._make_api()
        self.status_var.set("Pengaturan disimpan, menghubungkan...")

        def done(res, err):
            if err:
                messagebox.showwarning("Koneksi", "Server tidak bisa dihubungi:\n%s" % err)
            else:
                self.busy = False
        self._bg(lambda: self.api.call("devices"), done)


if __name__ == "__main__":
    CentralApp().mainloop()
