"""
Sprint 28 Tests: /personality slash command — backend API coverage.
Tests: GET /api/personalities, POST /api/personality/set, Session.compact(),
path traversal defence, size cap, clear personality.
"""
import json
import pathlib
import shutil
import sys
import urllib.error
import urllib.request

# Import test constants from conftest (same process — these are module-level values)
sys.path.insert(0, str(pathlib.Path(__file__).parent))
from conftest import TEST_STATE_DIR

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 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 _personalities_dir():
    """Return the personalities directory the test server will look in.

    conftest sets HERMES_HOME=TEST_STATE_DIR in the server's environment.
    The server's api/profiles._DEFAULT_HERMES_HOME resolves to TEST_STATE_DIR,
    so get_active_hermes_home() returns TEST_STATE_DIR, and personalities
    live at TEST_STATE_DIR/personalities.
    """
    p = TEST_STATE_DIR / 'personalities'
    p.mkdir(parents=True, exist_ok=True)
    return p


def _make_personality(name, content="# Test Bot\nA test personality."):
    """Create a personality directory with a SOUL.md."""
    d = _personalities_dir() / name
    d.mkdir(parents=True, exist_ok=True)
    (d / "SOUL.md").write_text(content)
    return d


def _make_session():
    """Create a new session and return its session_id."""
    d, status = post("/api/session/new", {})
    assert status == 200, f"Failed to create session: {d}"
    return d["session"]["session_id"]


def _cleanup_session(sid):
    try:
        post("/api/session/delete", {"session_id": sid})
    except Exception:
        pass


# ── GET /api/personalities ────────────────────────────────────────────────────

def test_personalities_empty_when_none_exist():
    """GET /api/personalities returns empty list when no personalities exist."""
    p_dir = _personalities_dir()
    for child in list(p_dir.iterdir()):
        if child.is_dir() and not child.is_symlink():
            shutil.rmtree(child)
    d, status = get("/api/personalities")
    assert status == 200
    assert d.get("personalities") == []


def test_personalities_lists_from_config():
    """GET /api/personalities returns personalities from config.yaml agent.personalities.
    Skipped if no personalities configured in test environment.
    """
    d, status = get("/api/personalities")
    assert status == 200
    assert isinstance(d.get("personalities"), list)
    # If personalities are configured, verify structure
    for p in d.get("personalities", []):
        assert "name" in p
        assert "description" in p


def test_personalities_returns_empty_when_none_configured():
    """GET /api/personalities returns empty list when no personalities in config."""
    # The test server starts with a clean state dir (no config.yaml),
    # so agent.personalities is empty by default
    d, status = get("/api/personalities")
    assert status == 200
    # May or may not have personalities depending on the real ~/.hermes/config.yaml
    # being loaded. Just verify the structure is correct.
    assert isinstance(d.get("personalities"), list)


def test_personalities_skips_non_dict_config():
    """GET /api/personalities handles non-dict agent config gracefully."""
    d, status = get("/api/personalities")
    assert status == 200
    assert isinstance(d.get("personalities"), list)


# ── POST /api/personality/set ─────────────────────────────────────────────────

_test_personalities = {}

def _inject_personality(name, value):
    """Write a personality into the test config.yaml so the server picks it up."""
    _test_personalities[name] = value
    _write_test_config()

def _remove_personality(name):
    """Remove a personality from the test config.yaml."""
    _test_personalities.pop(name, None)
    _write_test_config()

def _write_test_config():
    """Write config.yaml with test personalities using simple YAML format."""
    TEST_STATE_DIR.mkdir(parents=True, exist_ok=True)
    config_path = TEST_STATE_DIR / 'config.yaml'
    lines = ['agent:', '  personalities:']
    for pname, pval in _test_personalities.items():
        if isinstance(pval, dict):
            lines.append(f'    {pname}:')
            for k, v in pval.items():
                lines.append(f'      {k}: "{v}"')
        else:
            lines.append(f'    {pname}: "{pval}"')
    config_path.write_text('\n'.join(lines) + '\n')


def test_set_personality_valid():
    """Setting a personality that exists in config stores name and returns prompt.
    Skipped if config.yaml has no personalities (common in test environments).
    """
    # First check if any personalities are configured
    d, status = get("/api/personalities")
    if not d.get("personalities"):
        return  # skip — no personalities in test server config
    name = d["personalities"][0]["name"]
    sid = _make_session()
    try:
        d2, status2 = post("/api/personality/set", {"session_id": sid, "name": name})
        assert status2 == 200
        assert d2.get("ok") is True
        assert d2.get("personality") == name
    finally:
        _cleanup_session(sid)


def test_set_personality_persists_in_compact():
    """After setting personality, GET /api/session returns personality in compact.
    Skipped if config.yaml has no personalities.
    """
    d, status = get("/api/personalities")
    if not d.get("personalities"):
        return  # skip
    name = d["personalities"][0]["name"]
    sid = _make_session()
    try:
        post("/api/personality/set", {"session_id": sid, "name": name})
        d2, status2 = get(f"/api/session?session_id={sid}")
        assert status2 == 200
        session = d2.get("session", {})
        assert session.get("personality") == name
    finally:
        _cleanup_session(sid)


def test_clear_personality_sets_null():
    """Clearing personality with name='' sets it to None (null in JSON)."""
    sid = _make_session()
    try:
        # Set a personality name directly on the session (no config validation needed for clear)
        d, status = post("/api/personality/set", {"session_id": sid, "name": ""})
        assert status == 200
        assert d.get("personality") is None
        # Verify persisted
        d2, s2 = get(f"/api/session?session_id={sid}")
        assert s2 == 200
        assert d2.get("session", {}).get("personality") is None
    finally:
        _cleanup_session(sid)


def test_set_personality_not_found_returns_404():
    """Setting a non-existent personality returns 404."""
    sid = _make_session()
    try:
        d, status = post("/api/personality/set",
                         {"session_id": sid, "name": "doesnotexist"})
        assert status == 404
    finally:
        _cleanup_session(sid)


def test_set_personality_nonexistent_returns_404():
    """Names not in config.yaml agent.personalities return 404."""
    sid = _make_session()
    try:
        d, status = post("/api/personality/set",
                         {"session_id": sid, "name": "doesnotexist"})
        assert status == 404, f"Expected 404, got {status}: {d}"
    finally:
        _cleanup_session(sid)


def test_set_personality_missing_session_returns_404():
    """Setting personality on non-existent session returns 404."""
    d, status = post("/api/personality/set",
                     {"session_id": "nonexistent000", "name": "x"})
    assert status == 404
