"""
E2E 测试共享工具 — 服务端启动、HTTP 封装、断言
"""
import subprocess
import time
import json
import requests
import os
import sys
import uuid

PORT = int(os.environ.get("THESEUS_PORT", "3080"))
BASE = f"http://127.0.0.1:{PORT}"
BINARY = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
                       "target", "release", "theseus")

_server_proc = None
RUN_ID = os.environ.get("THESEUS_E2E_RUN_ID") or uuid.uuid4().hex[:8]


def start_server():
    """启动 Theseus 服务端，等待就绪"""
    global _server_proc
    stop_server()
    _server_proc = subprocess.Popen(
        [BINARY, "serve", "--port", str(PORT)],
        stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
    )
    for _ in range(30):
        time.sleep(0.3)
        try:
            r = requests.get(f"{BASE}/api/v1/projects", timeout=2)
            if r.status_code == 200:
                return
        except requests.ConnectionError:
            pass
    raise RuntimeError("服务端启动超时")


def stop_server():
    """停止服务端"""
    global _server_proc
    if _server_proc:
        _server_proc.kill()
        _server_proc.wait()
        _server_proc = None


# ── HTTP helpers ─────────────────────────────────────────

def url(path):
    return f"{BASE}{path}"


def get(path, **kw):
    return requests.get(url(path), timeout=10, **kw)


def post(path, data=None, files=None):
    if files:
        return requests.post(url(path), files=files, timeout=10)
    return requests.post(url(path), json=data, timeout=10)


def put(path, data=None):
    return requests.put(url(path), json=data, timeout=10)


def delete(path, params=None):
    return requests.delete(url(path), params=params, timeout=10)


# ── Assertions ───────────────────────────────────────────

def assert_ok(resp, msg=""):
    """断言 API 返回 success: true"""
    body = resp.json()
    assert body.get("success"), f"{msg} — {body.get('error', 'no error')}"
    return body.get("data")


def assert_err(resp, status, msg=""):
    """断言 API 返回指定 HTTP 状态码"""
    assert resp.status_code == status, \
        f"{msg} — expected {status}, got {resp.status_code}: {resp.text[:200]}"


def assert_status(resp, expected, msg=""):
    assert resp.status_code == expected, \
        f"{msg} — expected {expected}, got {resp.status_code}: {resp.text[:200]}"


# ── Fixture helpers ──────────────────────────────────────

def unique_name(name):
    return f"{name}-{RUN_ID}"


def create_project(name="e2e-test"):
    data = assert_ok(post("/api/v1/projects", {"name": name}))
    return data["id"]


def create_node(pid, label, node_type="service", content="## test", after=None, edge_label=None):
    body = {"label": label, "node_type": node_type, "properties": {"content": content}}
    if after:
        body["after"] = after
    if edge_label:
        body["edge_label"] = edge_label
    return assert_ok(post(f"/api/v1/projects/{pid}/nodes", body))


def delete_project(pid):
    assert_ok(delete(f"/api/v1/projects/{pid}"))


def delete_node(pid, nid):
    assert_ok(delete(f"/api/v1/projects/{pid}/nodes/{nid}"))


def delete_node_force(pid, nid):
    assert_ok(delete(f"/api/v1/projects/{pid}/nodes/{nid}", params={"force": "true"}))


# ── Print helpers ────────────────────────────────────────

PASS = 0
FAIL = 0


def ok(name):
    global PASS
    PASS += 1
    print(f"  ✅ {name}")


def fail(name, reason=""):
    global FAIL
    FAIL += 1
    print(f"  ❌ {name} — {reason}")


def header(text):
    print(f"\n{'='*60}")
    print(f"  {text}")
    print(f"{'='*60}")


def summary():
    print(f"\n{'='*60}")
    total = PASS + FAIL
    if FAIL == 0:
        print(f"  ✅ ALL {total} tests passed")
    else:
        print(f"  ❌ {PASS}/{total} passed, {FAIL} failed")
    print(f"{'='*60}\n")
    return FAIL == 0
