"""PC Agent - dipasang di setiap komputer/laptop yang dimonitor.

Tugas: kirim aktivitas ke server tiap beberapa detik, ambil perintah dari Central
(kunci layar, blokir internet, batas tab, situs terlarang), dan menjalankannya.

Jalankan:  PCMonAgent.exe            (normal)
           PCMonAgent.exe --reset    (buka blokir internet + kunci, lalu keluar)
"""
import json
import os
import sys
import threading
import time
import uuid
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

import netblock
import sysinfo
from common import Api, ApiError

LOCAL_PORT = 47631
TABS_STALE_SECONDS = 90
DEFAULT_STATE = {"locked": False, "internet_blocked": False, "max_tabs": 0,
                 "blocked_sites": [], "lock_message": ""}


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


def load_config(path=None):
    path = path or os.path.join(base_dir(), "agent_config.json")
    with open(path, "r", encoding="utf-8") as fh:
        cfg = json.load(fh)
    cfg.setdefault("interval", 3)
    cfg.setdefault("device_name", "")
    return cfg


def state_dir():
    candidates = [os.path.join(os.environ.get("PROGRAMDATA", ""), "PCMon"), os.path.join(base_dir(), "data")]
    for path in candidates:
        if not path or path.startswith("PCMon"):
            continue
        try:
            os.makedirs(path, exist_ok=True)
            probe = os.path.join(path, ".w")
            with open(probe, "w") as fh:
                fh.write("1")
            os.remove(probe)
            return path
        except OSError:
            continue
    return base_dir()


class AgentCore:
    """Logika agent tanpa GUI (supaya bisa dites)."""

    def __init__(self, cfg, api, blocker, si=sysinfo, state_path=None):
        self.cfg = cfg
        self.api = api
        self.blocker = blocker
        self.si = si
        self.lock = threading.Lock()
        self.state_path = state_path
        self.desired = dict(DEFAULT_STATE)
        self.tabs = []
        self.tabs_ts = 0.0
        self.online = False
        self.last_error = ""
        self.device_id = uuid.uuid4().hex
        self._load_cache()

    # -- cache lokal: kunci/blokir tetap berlaku setelah restart, walau server mati --
    def _load_cache(self):
        if not self.state_path or not os.path.exists(self.state_path):
            self._save_cache()
            return
        try:
            with open(self.state_path, "r", encoding="utf-8") as fh:
                data = json.load(fh)
            self.device_id = data.get("device_id", self.device_id)
            self.desired.update(data.get("desired", {}))
        except (OSError, ValueError):
            pass

    def _save_cache(self):
        if not self.state_path:
            return
        try:
            with open(self.state_path, "w", encoding="utf-8") as fh:
                json.dump({"device_id": self.device_id, "desired": self.desired}, fh)
        except OSError:
            pass

    # -- tab dari ekstensi Chrome ------------------------------------------
    def set_tabs(self, tabs):
        clean = []
        for t in tabs[:300]:
            if isinstance(t, dict):
                clean.append({"title": str(t.get("title", ""))[:200], "url": str(t.get("url", ""))[:500]})
        with self.lock:
            self.tabs = clean
            self.tabs_ts = time.time()

    def current_tabs(self):
        with self.lock:
            if time.time() - self.tabs_ts > TABS_STALE_SECONDS:
                return []  # Chrome ditutup / ekstensi tidak aktif
            return list(self.tabs)

    def snapshot_state(self):
        with self.lock:
            return dict(self.desired)

    # -- sinkron ------------------------------------------------------------
    def build_payload(self):
        title, proc = self.si.get_active_window()
        name = self.cfg.get("device_name") or self.si.hostname()
        return {
            "device_id": self.device_id,
            "name": name,
            "os": self.si.os_name(),
            "active_window": title,
            "active_process": proc,
            "windows": self.si.list_windows(),
            "processes": self.si.list_processes(),
            "tabs": self.current_tabs(),
        }

    def sync_once(self):
        """Satu putaran sinkron. Return True bila server terjangkau."""
        try:
            res = self.api.call("agent_sync", self.build_payload())
        except ApiError as exc:
            self.online = False
            self.last_error = str(exc)
            self.blocker.apply(self.snapshot_state()["internet_blocked"])  # tetap pakai status terakhir
            return False
        self.online = True
        self.last_error = ""
        with self.lock:
            self.desired.update(res.get("state", {}))
        self._save_cache()
        self.blocker.apply(self.snapshot_state()["internet_blocked"])
        return True

    def run_forever(self, stop_event):
        interval = max(1, float(self.cfg.get("interval", 3)))
        while not stop_event.is_set():
            try:
                self.sync_once()
            except Exception as exc:  # jangan sampai thread mati
                self.last_error = repr(exc)
            stop_event.wait(interval)


# -- server lokal untuk ekstensi Chrome (127.0.0.1 saja) ------------------------
def make_local_handler(core):
    class Handler(BaseHTTPRequestHandler):
        def log_message(self, *args):
            pass

        def _send(self, code, obj):
            body = json.dumps(obj).encode("utf-8")
            self.send_response(code)
            self.send_header("Content-Type", "application/json")
            self.send_header("Content-Length", str(len(body)))
            self.send_header("Access-Control-Allow-Origin", "*")
            self.send_header("Access-Control-Allow-Headers", "Content-Type")
            self.send_header("Cache-Control", "no-store")
            self.end_headers()
            self.wfile.write(body)

        def do_OPTIONS(self):
            self._send(204, {})

        def do_GET(self):
            if self.path.split("?")[0] == "/state":
                self._send(200, {"ok": True, "state": core.snapshot_state()})
            else:
                self._send(404, {"ok": False})

        def do_POST(self):
            if self.path.split("?")[0] != "/tabs":
                return self._send(404, {"ok": False})
            # hanya ekstensi browser yang boleh mengirim data tab, bukan halaman web biasa
            origin = self.headers.get("Origin", "")
            if not (origin.startswith("chrome-extension://") or origin.startswith("moz-extension://")):
                return self._send(403, {"ok": False, "error": "origin ditolak"})
            try:
                length = min(int(self.headers.get("Content-Length", "0")), 1_000_000)
                data = json.loads(self.rfile.read(length).decode("utf-8"))
                tabs = data.get("tabs", [])
                if not isinstance(tabs, list):
                    raise ValueError("tabs bukan list")
            except (ValueError, TypeError):
                return self._send(400, {"ok": False, "error": "JSON salah"})
            core.set_tabs(tabs)
            self._send(200, {"ok": True, "state": core.snapshot_state()})

    return Handler


def start_local_server(core, port=LOCAL_PORT):
    server = ThreadingHTTPServer(("127.0.0.1", port), make_local_handler(core))
    threading.Thread(target=server.serve_forever, daemon=True).start()
    return server


# -- GUI -----------------------------------------------------------------------
def show_notice(root, text="Komputer ini dimonitor oleh administrator.", ms=6000):
    import tkinter as tk
    win = tk.Toplevel(root)
    win.overrideredirect(True)
    win.attributes("-topmost", True)
    win.configure(bg="#1f2937")
    tk.Label(win, text=text, bg="#1f2937", fg="#f3f4f6", font=("Segoe UI", 10), padx=16, pady=10).pack()
    win.update_idletasks()
    x = win.winfo_screenwidth() - win.winfo_reqwidth() - 20
    y = win.winfo_screenheight() - win.winfo_reqheight() - 70
    win.geometry("+%d+%d" % (x, y))
    win.after(ms, win.destroy)


def main(argv=None):
    argv = argv if argv is not None else sys.argv[1:]
    cfg = load_config()
    blocker = netblock.NetBlocker(cfg["server_url"])

    if "--reset" in argv:
        from lockscreen import set_taskmgr_disabled
        blocker.reset()
        set_taskmgr_disabled(False)
        print("Blokir internet dan kunci dibuka.")
        return 0

    api = Api(cfg["server_url"], cfg["agent_key"])
    core = AgentCore(cfg, api, blocker, state_path=os.path.join(state_dir(), "agent_state.json"))

    import tkinter as tk
    from lockscreen import LockScreen

    root = tk.Tk()
    root.withdraw()
    lockscreen = LockScreen(root)

    try:
        start_local_server(core)
    except OSError:
        pass  # port terpakai (agent kedua?) - fitur ekstensi nonaktif

    stop = threading.Event()
    threading.Thread(target=core.run_forever, args=(stop,), daemon=True).start()

    def tick():
        st = core.snapshot_state()
        lockscreen.set(st["locked"], st["lock_message"])
        root.after(300, tick)

    tick()
    show_notice(root)
    try:
        root.mainloop()
    finally:
        stop.set()
        lockscreen.hide()
    return 0


if __name__ == "__main__":
    sys.exit(main())
