"""GET/PUT /api/v1/realism/personas — global persona pool CRUD."""
from __future__ import annotations

import json

import pytest

from decnet.realism import personas_pool as global_pool
from decnet.web.router.realism.api_personas import (
    list_personas,
    replace_personas,
)


@pytest.fixture(autouse=True)
def _reset_pool():
    global_pool.reset_cache()
    yield
    global_pool.reset_cache()


_VALID = [
    {
        "name": "John Smith",
        "email": "john@corp.com",
        "role": "COO",
        "tone": "formal",
        "mannerisms": ["uses 'Best regards'"],
    },
    {
        "name": "Sarah Johnson",
        "email": "sarah@corp.com",
        "role": "PM",
        "tone": "direct",
        "mannerisms": ["uses bullets"],
    },
]


@pytest.mark.asyncio
async def test_list_returns_empty_when_no_pool(tmp_path, monkeypatch):
    monkeypatch.setenv(
        "DECNET_REALISM_PERSONAS", str(tmp_path / "missing.json"),
    )
    result = await list_personas(user={"uuid": "u", "role": "viewer"})
    assert result["personas"] == []
    assert result["path"].endswith("missing.json")


@pytest.mark.asyncio
async def test_list_returns_existing_pool(tmp_path, monkeypatch):
    pool = tmp_path / "pool.json"
    pool.write_text(json.dumps(_VALID))
    monkeypatch.setenv("DECNET_REALISM_PERSONAS", str(pool))

    result = await list_personas(user={"uuid": "u", "role": "viewer"})
    assert len(result["personas"]) == 2
    assert {p["email"] for p in result["personas"]} == {
        "john@corp.com", "sarah@corp.com",
    }


@pytest.mark.asyncio
async def test_replace_writes_canonical_file(tmp_path, monkeypatch):
    dest = tmp_path / "pool.json"
    monkeypatch.setenv("DECNET_REALISM_PERSONAS", str(dest))

    result = await replace_personas(
        body={"personas": _VALID},
        user={"uuid": "u", "role": "admin", "username": "anti"},
    )
    assert len(result["personas"]) == 2
    assert dest.exists()
    written = json.loads(dest.read_text())
    assert {p["email"] for p in written} == {
        "john@corp.com", "sarah@corp.com",
    }


@pytest.mark.asyncio
async def test_replace_with_empty_list_clears_pool(tmp_path, monkeypatch):
    """Operator deliberately wiping the pool is allowed — empty list is
    valid and means "no fleet personas, skip fleet mail deckies"."""
    dest = tmp_path / "pool.json"
    dest.write_text(json.dumps(_VALID))
    monkeypatch.setenv("DECNET_REALISM_PERSONAS", str(dest))

    result = await replace_personas(
        body={"personas": []},
        user={"uuid": "u", "role": "admin", "username": "anti"},
    )
    assert result["personas"] == []
    assert json.loads(dest.read_text()) == []


@pytest.mark.asyncio
async def test_replace_rejects_non_list_payload(tmp_path, monkeypatch):
    from fastapi import HTTPException

    monkeypatch.setenv(
        "DECNET_REALISM_PERSONAS", str(tmp_path / "pool.json"),
    )
    with pytest.raises(HTTPException) as exc:
        await replace_personas(
            body={"personas": "not-a-list"},
            user={"uuid": "u", "role": "admin", "username": "anti"},
        )
    assert exc.value.status_code == 400


@pytest.mark.asyncio
async def test_replace_rejects_all_invalid_payload(tmp_path, monkeypatch):
    """Sending a non-empty list where *every* entry is invalid is almost
    certainly an operator schema mistake — fail loudly rather than
    silently writing an empty pool."""
    from fastapi import HTTPException

    monkeypatch.setenv(
        "DECNET_REALISM_PERSONAS", str(tmp_path / "pool.json"),
    )
    with pytest.raises(HTTPException) as exc:
        await replace_personas(
            body={"personas": [{"name": "broken", "email": "no-at-symbol"}]},
            user={"uuid": "u", "role": "admin", "username": "anti"},
        )
    assert exc.value.status_code == 400
    assert "validation" in exc.value.detail.lower()


@pytest.mark.asyncio
async def test_replace_drops_partially_invalid_entries(tmp_path, monkeypatch):
    """One bad apple doesn't kill the request — invalid entries get
    dropped, valid ones land, response shows what stuck."""
    dest = tmp_path / "pool.json"
    monkeypatch.setenv("DECNET_REALISM_PERSONAS", str(dest))

    result = await replace_personas(
        body={"personas": [
            _VALID[0],
            {"name": "broken", "email": "no-at-symbol"},
            _VALID[1],
        ]},
        user={"uuid": "u", "role": "admin", "username": "anti"},
    )
    assert len(result["personas"]) == 2
    assert {p["email"] for p in result["personas"]} == {
        "john@corp.com", "sarah@corp.com",
    }


@pytest.mark.asyncio
async def test_get_then_put_round_trips_through_pool(tmp_path, monkeypatch):
    """The worker reads the same file the API writes — verify the
    write-then-read cycle leaves the pool in the expected state."""
    dest = tmp_path / "pool.json"
    monkeypatch.setenv("DECNET_REALISM_PERSONAS", str(dest))

    await replace_personas(
        body={"personas": _VALID},
        user={"uuid": "u", "role": "admin", "username": "anti"},
    )
    listed = await list_personas(user={"uuid": "u", "role": "viewer"})
    assert {p["email"] for p in listed["personas"]} == {
        "john@corp.com", "sarah@corp.com",
    }
    # And the worker's loader sees the same data.
    loaded = global_pool.load()
    assert {p.email for p in loaded} == {
        "john@corp.com", "sarah@corp.com",
    }
