"""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()

from tests._pytest_port import BASE

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'id="mainTasks"' in raw, "Tasks main-view not found in HTML"
    assert b'id="settingsMenu"' in raw, "Settings left-rail menu 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
