"""Sprint 6 tests: Escape from editor, Phase D validation, HTML extraction, cron create, session export."""
import json, uuid, pathlib, urllib.request, urllib.error
REPO_ROOT = pathlib.Path(__file__).parent.parent.resolve()

BASE = "http://127.0.0.1:8788"  # isolated test server

def get(path):
    with urllib.request.urlopen(BASE + path, timeout=10) as r:
        return json.loads(r.read()), r.status

def get_raw(path):
    with urllib.request.urlopen(BASE + path, timeout=10) as r:
        return r.read(), r.headers, r.status

def post(path, body=None):
    data = json.dumps(body or {}).encode()
    req = urllib.request.Request(BASE + path, data=data, headers={"Content-Type": "application/json"})
    try:
        with urllib.request.urlopen(req, timeout=10) as r:
            return json.loads(r.read()), r.status
    except urllib.error.HTTPError as e:
        return json.loads(e.read()), e.code

def make_session_tracked(created_list, ws=None):
    body = {}
    if ws: body["workspace"] = str(ws)
    d, _ = post("/api/session/new", body)
    sid = d["session"]["session_id"]
    created_list.append(sid)
    return sid, pathlib.Path(d["session"]["workspace"])

# ── Phase E: HTML served from static/index.html ──

def test_index_html_served():
    raw, headers, status = get_raw("/")
    assert status == 200
    assert b"sidebarResize" in raw, "Resize handle not found in HTML"
    assert b"cronCreateForm" in raw, "Cron create form not found in HTML"
    assert b"btnExportJSON" in raw, "Export JSON button not found in HTML"

def test_index_html_file_exists():
    p = REPO_ROOT / "static/index.html"
    assert p.exists(), "static/index.html does not exist"
    assert p.stat().st_size > 5000, "index.html seems too small"

def test_server_py_has_no_html_string():
    txt = (REPO_ROOT / "server.py").read_text()
    assert 'HTML = r"""' not in txt, "server.py still contains inline HTML string"
    assert "doctype html" not in txt.lower(), "server.py still contains raw HTML"

# ── Phase D: remaining endpoint validation ──

def test_approval_respond_requires_session_id():
    result, status = post("/api/approval/respond", {"choice": "deny"})
    assert status == 400

def test_approval_respond_rejects_invalid_choice(cleanup_test_sessions):
    sid, _ = make_session_tracked(cleanup_test_sessions)
    result, status = post("/api/approval/respond", {"session_id": sid, "choice": "INVALID"})
    assert status == 400

def test_file_raw_requires_session_id():
    try:
        get_raw("/api/file/raw?path=test.png")
        assert False, "Expected 400"
    except urllib.error.HTTPError as e:
        assert e.code == 400

def test_file_raw_unknown_session():
    try:
        get_raw("/api/file/raw?session_id=nosuchsession&path=test.png")
        assert False, "Expected 404"
    except urllib.error.HTTPError as e:
        assert e.code == 404

# ── Cron create ──

def test_cron_create_requires_prompt():
    result, status = post("/api/crons/create", {"schedule": "0 9 * * *"})
    assert status == 400
    assert "prompt" in result.get("error", "").lower()

def test_cron_create_requires_schedule():
    result, status = post("/api/crons/create", {"prompt": "Say hello"})
    assert status == 400
    assert "schedule" in result.get("error", "").lower()

def test_cron_create_invalid_schedule():
    result, status = post("/api/crons/create", {
        "prompt": "Say hello", "schedule": "not_a_valid_schedule_xyz"
    })
    assert status == 400

def test_cron_create_success():
    uid = uuid.uuid4().hex[:6]
    result, status = post("/api/crons/create", {
        "name": f"test-job-{uid}",
        "prompt": "Just say 'hello' and nothing else.",
        "schedule": "every 999h",  # far future -- won't actually run during test
        "deliver": "local",
    })
    assert status == 200, f"Expected 200 got {status}: {result}"
    assert result["ok"] is True
    assert "job" in result
    job_id = result["job"]["id"]
    # Verify it appears in the cron list
    jobs, _ = get("/api/crons")
    ids = [j["id"] for j in jobs["jobs"]]
    assert job_id in ids, f"Created job {job_id} not in list"

# ── Session export ──

def test_session_export_requires_session_id():
    try:
        get_raw("/api/session/export")
        assert False
    except urllib.error.HTTPError as e:
        assert e.code == 400

def test_session_export_unknown_session():
    try:
        get_raw("/api/session/export?session_id=nosuchsession")
        assert False
    except urllib.error.HTTPError as e:
        assert e.code == 404

def test_session_export_returns_json(cleanup_test_sessions):
    sid, _ = make_session_tracked(cleanup_test_sessions)
    raw, headers, status = get_raw(f"/api/session/export?session_id={sid}")
    assert status == 200
    assert "application/json" in headers.get("Content-Type", "")
    data = json.loads(raw)
    assert data["session_id"] == sid
    assert "messages" in data
    assert "title" in data

# ── Resizable panels: static files present ──

def test_static_index_has_resize_handles():
    raw, _, status = get_raw("/")
    assert status == 200
    assert b"sidebarResize" in raw
    assert b"rightpanelResize" in raw

def test_app_js_has_resize_logic():
    """Sprint 9: app.js replaced by modules. Resize logic lives in boot.js."""
    raw, _, status = get_raw("/static/boot.js")
    assert status == 200
    assert b"_initResizePanels" in raw
    assert b"hermes-sidebar-w" in raw
    assert b"hermes-panel-w" in raw
