"""
Sprint 12 Tests: settings panel, session pinning, session import, SSE reconnect.
"""
import json, pathlib, urllib.error, urllib.request, urllib.parse

BASE = "http://127.0.0.1:8788"


def get(path):
    with urllib.request.urlopen(BASE + path, timeout=10) as r:
        return json.loads(r.read()), 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(created_list):
    d, _ = post("/api/session/new", {})
    sid = d["session"]["session_id"]
    created_list.append(sid)
    return sid


# ── Settings API ──────────────────────────────────────────────────────────

def test_settings_get_returns_defaults():
    """GET /api/settings returns default settings."""
    d, status = get("/api/settings")
    assert status == 200
    assert 'default_model' in d
    assert 'default_workspace' in d

def test_settings_post_persists_general_settings():
    """POST /api/settings saves general UI settings."""
    d, status = post("/api/settings", {"send_key": "ctrl+enter", "theme": "nord"})
    assert status == 200
    assert d['send_key'] == 'ctrl+enter'
    assert d['theme'] == 'nord'
    # Verify it persisted
    d2, _ = get("/api/settings")
    assert d2['send_key'] == 'ctrl+enter'
    assert d2['theme'] == 'nord'
    # Restore
    post("/api/settings", {"send_key": "enter", "theme": "dark"})

def test_settings_default_model_is_read_only():
    """default_model in /api/settings mirrors config.yaml and ignores stale writes."""
    models, _ = get("/api/models")
    expected_model = models['default_model']
    d, status = post("/api/settings", {"default_model": "test/model-123"})
    assert status == 200
    assert d['default_model'] == expected_model
    d2, _ = get("/api/settings")
    assert d2['default_model'] == expected_model

def test_settings_partial_update():
    """POST /api/settings with partial data doesn't clobber other fields."""
    d1, _ = get("/api/settings")
    original_ws = d1['default_workspace']
    original_model = d1['default_model']
    post("/api/settings", {"show_cli_sessions": True})
    d2, _ = get("/api/settings")
    assert d2['show_cli_sessions'] is True
    assert d2['default_workspace'] == original_ws
    assert d2['default_model'] == original_model
    # Restore
    post("/api/settings", {"show_cli_sessions": False})

def test_agent_settings_copy_clarifies_sidebar_vs_defaults():
    """Agent settings explain that sidebar model selection is per-chat."""
    html = pathlib.Path("static/index.html").read_text(encoding="utf-8")
    assert "Applies to this chat. Profile defaults live in Settings -> Agent." in html
    assert "These are profile defaults. The model picker in the left sidebar only changes the current chat." in html

def test_agent_settings_hide_advanced_fields_behind_toggle():
    """Base URL and toolsets live behind an Advanced toggle with plain-language help."""
    html = pathlib.Path("static/index.html").read_text(encoding="utf-8")
    js = pathlib.Path("static/panels.js").read_text(encoding="utf-8")
    assert 'id="agentConfigAdvancedToggle"' in html
    assert 'id="agentConfigAdvancedFields" style="display:none"' in html
    assert "Custom API Base URL" in html
    assert "Most users should leave these as-is." in html
    assert "function toggleAgentAdvancedSettings()" in js
    assert "function setAgentAdvancedSettingsOpen(open)" in js


# ── Session Pinning ───────────────────────────────────────────────────────

def test_pin_session():
    """POST /api/session/pin sets pinned=true."""
    created = []
    try:
        sid = make_session(created)
        d, status = post("/api/session/pin", {"session_id": sid, "pinned": True})
        assert status == 200
        assert d['ok'] is True
        assert d['session']['pinned'] is True
    finally:
        for sid in created:
            post("/api/session/delete", {"session_id": sid})

def test_unpin_session():
    """POST /api/session/pin with pinned=false unpins."""
    created = []
    try:
        sid = make_session(created)
        post("/api/session/pin", {"session_id": sid, "pinned": True})
        d, status = post("/api/session/pin", {"session_id": sid, "pinned": False})
        assert status == 200
        assert d['session']['pinned'] is False
    finally:
        for sid in created:
            post("/api/session/delete", {"session_id": sid})

def test_pinned_in_session_list():
    """Pinned sessions include pinned field in session list."""
    created = []
    try:
        sid = make_session(created)
        # Pin it and give it a title so it shows in the list
        post("/api/session/rename", {"session_id": sid, "title": "Pinned Test"})
        post("/api/session/pin", {"session_id": sid, "pinned": True})
        d, _ = get("/api/sessions")
        match = [s for s in d['sessions'] if s['session_id'] == sid]
        assert len(match) == 1
        assert match[0]['pinned'] is True
    finally:
        for sid in created:
            post("/api/session/delete", {"session_id": sid})

def test_pinned_persists_on_reload():
    """Pin status survives session reload from disk."""
    created = []
    try:
        sid = make_session(created)
        post("/api/session/pin", {"session_id": sid, "pinned": True})
        d, _ = get(f"/api/session?session_id={sid}")
        assert d['session']['pinned'] is True
    finally:
        for sid in created:
            post("/api/session/delete", {"session_id": sid})


# ── Session Import ────────────────────────────────────────────────────────

def test_import_session_basic():
    """POST /api/session/import creates a new session from JSON."""
    payload = {
        "title": "Imported Test",
        "messages": [
            {"role": "user", "content": "Hello from import"},
            {"role": "assistant", "content": "Hi there!"},
        ],
        "model": "test/import-model",
    }
    d, status = post("/api/session/import", payload)
    assert status == 200
    assert d['ok'] is True
    sid = d['session']['session_id']
    try:
        assert d['session']['title'] == 'Imported Test'
        assert len(d['session']['messages']) == 2
        # Verify it loads correctly
        d2, _ = get(f"/api/session?session_id={sid}")
        assert d2['session']['model'] == 'test/import-model'
    finally:
        post("/api/session/delete", {"session_id": sid})

def test_import_requires_messages():
    """Import fails without a messages array."""
    d, status = post("/api/session/import", {"title": "No messages"})
    assert status == 400

def test_import_creates_new_id():
    """Imported session gets a new session_id, not reusing any from the payload."""
    payload = {
        "session_id": "should_be_ignored",
        "title": "ID Test",
        "messages": [{"role": "user", "content": "test"}],
    }
    d, _ = post("/api/session/import", payload)
    sid = d['session']['session_id']
    try:
        # The import should create a new ID, not use the one from the payload
        assert sid != "should_be_ignored"
    finally:
        post("/api/session/delete", {"session_id": sid})

def test_import_with_pinned():
    """Imported session can be pinned."""
    payload = {
        "title": "Pinned Import",
        "messages": [{"role": "user", "content": "test"}],
        "pinned": True,
    }
    d, _ = post("/api/session/import", payload)
    sid = d['session']['session_id']
    try:
        d2, _ = get(f"/api/session?session_id={sid}")
        assert d2['session']['pinned'] is True
    finally:
        post("/api/session/delete", {"session_id": sid})
