"""Uji NYATA terhadap Google Chrome (headless): batas tab, blokir situs, dan download-harus-disetujui lewat CDP."""
import asyncio
import http.server
import json
import shutil
import socket
import subprocess
import tempfile
import threading
import time
import urllib.request
from pathlib import Path

import pytest

from modules.browser_manager import BrowserController, Hooks, find_browser

pytestmark = pytest.mark.skipif(not (find_browser("chrome") or shutil.which("google-chrome")), reason="Chrome tidak terpasang")
CHROME = find_browser("chrome") or shutil.which("google-chrome")
PDF = b"%PDF-1.4\n% isi berkas uji\n" + b"x" * 2000


class Handler(http.server.BaseHTTPRequestHandler):
    def log_message(self, *a):
        pass

    def do_GET(self):
        if self.path.startswith("/file.pdf"):
            self.send_response(200)
            self.send_header("Content-Type", "application/octet-stream")
            self.send_header("Content-Disposition", 'attachment; filename="tugas.pdf"')
            self.send_header("Content-Length", str(len(PDF)))
            self.end_headers()
            self.wfile.write(PDF)
        else:
            body = b"<html><title>halaman uji</title><body>ok</body></html>"
            self.send_response(200)
            self.send_header("Content-Type", "text/html")
            self.send_header("Content-Length", str(len(body)))
            self.end_headers()
            self.wfile.write(body)


def free_port():
    s = socket.socket()
    s.bind(("127.0.0.1", 0))
    p = s.getsockname()[1]
    s.close()
    return p


@pytest.fixture()
def env():
    web_port, cdp_port = free_port(), free_port()
    srv = http.server.ThreadingHTTPServer(("127.0.0.1", web_port), Handler)
    threading.Thread(target=srv.serve_forever, daemon=True).start()
    tmp = Path(tempfile.mkdtemp())
    proc = subprocess.Popen([CHROME, "--headless=new", "--no-sandbox", "--disable-gpu", f"--user-data-dir={tmp / 'profile'}",
                             f"--remote-debugging-port={cdp_port}", "--remote-debugging-address=127.0.0.1", "--no-first-run",
                             "--host-resolver-rules=MAP *.uji.test 127.0.0.1", "about:blank"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    yield {"web": web_port, "cdp": cdp_port, "tmp": tmp}
    proc.terminate()
    try:
        proc.wait(5)
    except subprocess.TimeoutExpired:
        proc.kill()
    srv.shutdown()
    shutil.rmtree(tmp, ignore_errors=True)


def new_tab(cdp_port, url):
    req = urllib.request.Request(f"http://127.0.0.1:{cdp_port}/json/new?{url}", method="PUT")
    urllib.request.urlopen(req, timeout=5).read()


async def until(cond, timeout=10.0, step=0.1):
    t = time.time()
    while time.time() - t < timeout:
        if cond():
            return True
        await asyncio.sleep(step)
    return False


def policy(max_tabs=2, mode="blocklist", approval=True):
    return {"internet": {"mode": mode, "allow_domains": ["ok.uji.test"], "block_domains": ["blocked.uji.test"]},
            "browser": {"max_tabs": max_tabs, "downloads_require_approval": approval}}


async def scenario(env):
    ev = {"blocked": [], "tab_limit": [], "dl_blocked": [], "dl_used": [], "toast": []}

    async def on_blocked(k, d, r): ev["blocked"].append(d)
    async def on_tab(k, n): ev["tab_limit"].append(n)
    async def on_dl(k, u, n): ev["dl_blocked"].append((u, n))
    async def on_used(rid): ev["dl_used"].append(rid)
    async def on_toast(t): ev["toast"].append(t)

    pol = {"v": policy()}
    downloads, quarantine = env["tmp"] / "Downloads", env["tmp"] / "quarantine"
    ctrl = BrowserController("chrome", env["cdp"], Hooks(on_blocked, on_tab, on_dl, on_used, on_toast), lambda: downloads, quarantine, lambda: pol["v"])
    task = asyncio.create_task(ctrl.run())
    ok_url = f"http://ok.uji.test:{env['web']}/"
    try:
        assert await until(lambda: ctrl.connected), "controller tidak tersambung ke Chrome"
        await asyncio.sleep(0.5)

        # ---- 1. BATAS TAB: maksimal 2 ----------------------------------------------------------------------------
        for _ in range(3):
            await asyncio.to_thread(new_tab, env["cdp"], ok_url)
            await asyncio.sleep(0.4)
        await asyncio.sleep(1.0)
        real = json.loads(await asyncio.to_thread(lambda: urllib.request.urlopen(f"http://127.0.0.1:{env['cdp']}/json/list").read()))
        pages = [t for t in real if t["type"] == "page"]
        assert len(pages) <= 2, f"tab terbuka {len(pages)}, harusnya <= 2"
        assert ev["tab_limit"] and ev["tab_limit"][0] == 2

        # ---- 2. BLOKIR SITUS --------------------------------------------------------------------------------------------
        pol["v"] = policy(max_tabs=5)
        await ctrl.recheck_all()
        await asyncio.to_thread(new_tab, env["cdp"], f"http://blocked.uji.test:{env['web']}/")
        assert await until(lambda: "blocked.uji.test" in ev["blocked"]), "situs terlarang tidak terdeteksi"
        await asyncio.sleep(1.0)
        real = json.loads(await asyncio.to_thread(lambda: urllib.request.urlopen(f"http://127.0.0.1:{env['cdp']}/json/list").read()))
        assert not any("blocked.uji.test" in t["url"] and t["url"].startswith("http") for t in real), "halaman terlarang masih terbuka"
        assert any(t["url"].startswith("data:text/html") and t["type"] == "page" for t in real), "halaman blokir tidak ditampilkan"

        # ---- 3. DOWNLOAD DITAHAN ------------------------------------------------------------------------------------------
        file_url = f"http://ok.uji.test:{env['web']}/file.pdf"
        await asyncio.to_thread(new_tab, env["cdp"], file_url)
        assert await until(lambda: ev["dl_blocked"]), "permintaan download tidak terdeteksi"
        assert ev["dl_blocked"][0][0] == file_url and ev["dl_blocked"][0][1] == "tugas.pdf"
        await asyncio.sleep(1.0)
        assert not list(downloads.glob("*")) and not [p for p in quarantine.glob("*")], "berkas terlanjur terunduh tanpa persetujuan!"

        # ---- 4. DOWNLOAD DISETUJUI (sekali pakai) ---------------------------------------------------------------------
        await ctrl.add_approval(99, file_url, "tugas.pdf", 3)
        await asyncio.to_thread(new_tab, env["cdp"], file_url)
        assert await until(lambda: (downloads / "tugas.pdf").exists(), 15), "berkas yang disetujui tidak terunduh"
        assert (downloads / "tugas.pdf").read_bytes() == PDF
        assert ev["dl_used"] == [99]
        # izin sekali pakai: unduhan kedua harus ditahan lagi
        n_before = len(ev["dl_blocked"])
        await asyncio.sleep(1.0)
        await asyncio.to_thread(new_tab, env["cdp"], file_url)
        assert await until(lambda: len(ev["dl_blocked"]) > n_before, 10), "izin seharusnya sekali pakai"
        await asyncio.sleep(1.0)
        assert len(list(downloads.glob("*.pdf"))) == 1, "unduhan kedua tidak boleh lolos"

        # ---- 5. URL berbeda tidak ikut lolos saat jendela izin terbuka -----------------------------------------------------
        await ctrl.add_approval(100, file_url + "?lain=1", "lain.pdf", 3)
        other = f"http://ok.uji.test:{env['web']}/file.pdf?x=2"
        n_before = len(ev["dl_blocked"])
        await asyncio.to_thread(new_tab, env["cdp"], other)
        await asyncio.sleep(2.5)
        assert len(list(downloads.glob("*.pdf"))) <= 2  # varian query yang sama path boleh; yang penting tidak berantakan
    finally:
        ctrl.stop()
        task.cancel()
    return ev


def test_real_chrome_tab_limit_url_block_and_download_approval(env):
    ev = asyncio.run(scenario(env))
    assert ev["blocked"] and ev["tab_limit"]


async def scenario_allow_all(env):
    ev = {"dl": []}

    async def noop(*a): pass
    async def dl(k, u, n): ev["dl"].append(n)

    downloads, quarantine = env["tmp"] / "Downloads", env["tmp"] / "quarantine"
    ctrl = BrowserController("chrome", env["cdp"], Hooks(noop, noop, dl, noop, noop), lambda: downloads, quarantine, lambda: policy(0, "allow_all", approval=False))
    task = asyncio.create_task(ctrl.run())
    try:
        assert await until(lambda: ctrl.connected)
        await asyncio.sleep(0.5)
        for _ in range(4):  # max_tabs 0 = tanpa batas
            await asyncio.to_thread(new_tab, env["cdp"], f"http://ok.uji.test:{env['web']}/")
            await asyncio.sleep(0.3)
        await asyncio.sleep(1.0)
        real = json.loads(await asyncio.to_thread(lambda: urllib.request.urlopen(f"http://127.0.0.1:{env['cdp']}/json/list").read()))
        assert len([t for t in real if t["type"] == "page"]) >= 4
        await asyncio.to_thread(new_tab, env["cdp"], f"http://ok.uji.test:{env['web']}/file.pdf")
        assert await until(lambda: (downloads / "tugas.pdf").exists(), 15), "saat persetujuan dimatikan, download harus langsung jalan"
        assert not ev["dl"]
    finally:
        ctrl.stop()
        task.cancel()


def test_real_chrome_unrestricted_policy_does_not_interfere(env):
    asyncio.run(scenario_allow_all(env))
