from __future__ import annotations

import json
import threading
from http.server import BaseHTTPRequestHandler, HTTPServer
from types import SimpleNamespace

from acl_engine.actions import data as data_actions
from acl_engine.actions import file as file_actions
from acl_engine.actions import net as net_actions
from acl_engine.engine import ACLEngine


def build_runtime(tmp_path):
    return SimpleNamespace(workspace_root=tmp_path, timeout=5, memory={})


def test_net_request_auto_parses_json_and_query_params(tmp_path) -> None:
    runtime = build_runtime(tmp_path)

    class Handler(BaseHTTPRequestHandler):
        def do_GET(self) -> None:  # noqa: N802
            payload = {"path": self.path, "ok": True}
            body = json.dumps(payload).encode("utf-8")
            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(body)

        def log_message(self, format, *args):  # type: ignore[override]
            return

    server = HTTPServer(("127.0.0.1", 0), Handler)
    thread = threading.Thread(target=server.serve_forever)
    thread.start()

    try:
        url = f"http://127.0.0.1:{server.server_port}/users"
        result = net_actions.request(
            "GET",
            url,
            query={"name": "acl", "page": 2},
            runtime=runtime,
        )
    finally:
        server.shutdown()
        thread.join()
        server.server_close()

    assert result["status"] == 200
    assert result["ok"] is True
    assert result["json"]["ok"] is True
    assert "name=acl" in result["url"]
    assert "page=2" in result["url"]


def test_net_request_supports_json_body_without_manual_serialization(tmp_path) -> None:
    runtime = build_runtime(tmp_path)

    class Handler(BaseHTTPRequestHandler):
        def do_POST(self) -> None:  # noqa: N802
            content_length = int(self.headers.get("Content-Length", "0"))
            payload = json.loads(self.rfile.read(content_length).decode("utf-8"))
            response = json.dumps(
                {
                    "content_type": self.headers.get("Content-Type"),
                    "payload": payload,
                }
            ).encode("utf-8")
            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(response)

        def log_message(self, format, *args):  # type: ignore[override]
            return

    server = HTTPServer(("127.0.0.1", 0), Handler)
    thread = threading.Thread(target=server.serve_forever)
    thread.start()

    try:
        url = f"http://127.0.0.1:{server.server_port}/echo"
        result = net_actions.request(
            "POST",
            url,
            json_body={"hello": "world"},
            runtime=runtime,
        )
    finally:
        server.shutdown()
        thread.join()
        server.server_close()

    assert result["status"] == 200
    assert result["json"]["payload"] == {"hello": "world"}
    assert result["json"]["content_type"] == "application/json"


def test_file_json_actions_roundtrip_structured_data(tmp_path) -> None:
    runtime = build_runtime(tmp_path)
    payload = {"user": {"id": 7, "name": "Ada"}, "roles": ["admin"]}

    write_result = file_actions.write_json("state/user.json", payload, runtime=runtime)
    read_back = file_actions.read_json("state/user.json", runtime=runtime)

    assert write_result["written"] is True
    assert read_back == payload


def test_data_pick_and_omit_shape_objects(tmp_path) -> None:
    runtime = build_runtime(tmp_path)
    payload = {"id": 7, "name": "Ada", "secret": "hidden", "role": "admin"}

    picked = data_actions.pick(payload, ["id", "name", "role"], runtime=runtime)
    omitted = data_actions.omit(payload, ["secret"], runtime=runtime)

    assert picked == {"id": 7, "name": "Ada", "role": "admin"}
    assert omitted == {"id": 7, "name": "Ada", "role": "admin"}


def test_engine_api_flow_does_not_require_manual_json_parsing(tmp_path) -> None:
    class Handler(BaseHTTPRequestHandler):
        def do_GET(self) -> None:  # noqa: N802
            body = json.dumps(
                {
                    "user": {"id": 42, "name": "ACL"},
                    "meta": {"source": "test"},
                }
            ).encode("utf-8")
            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(body)

        def log_message(self, format, *args):  # type: ignore[override]
            return

    server = HTTPServer(("127.0.0.1", 0), Handler)
    thread = threading.Thread(target=server.serve_forever)
    thread.start()

    try:
        url = f"http://127.0.0.1:{server.server_port}/profile"
        engine = ACLEngine(workspace=tmp_path)
        acl_text = "\n".join(
            [
                f'net.request(method="GET", url="{url}") -> response',
                'data.pick(value=response.json, keys=["user", "meta"]) -> picked',
                'data.omit(value=picked, keys=["meta"]) -> compact',
                'file.write_json(path="profile.json", value=compact)',
                'file.read_json(path="profile.json") -> stored',
                'data.select(value=stored, path="user.name") -> name',
            ]
        )
        result = engine.execute(acl_text)
    finally:
        server.shutdown()
        thread.join()
        server.server_close()

    assert result["status"] == "success"
    assert result["data"]["name"] == "ACL"
    assert result["data"]["stored"] == {"user": {"id": 42, "name": "ACL"}}