"""GET/PUT /api/v1/realism/config — operator-tunable weights."""
from __future__ import annotations

import json
from unittest.mock import AsyncMock, patch

import pytest
from fastapi import HTTPException

from decnet.realism import planner


@pytest.fixture(autouse=True)
def _reset_planner():
    import decnet.web.router.realism.api_config as _api_config
    _api_config._hydrated = False
    yield
    planner.reset_to_defaults()
    _api_config._hydrated = False


@pytest.mark.asyncio
async def test_get_returns_defaults_when_no_row():
    from decnet.web.router.realism.api_config import get_config

    with patch("decnet.web.router.realism.api_config.repo") as mock_repo:
        mock_repo.get_realism_config = AsyncMock(return_value=None)

        result = await get_config(user={"uuid": "u", "role": "viewer"})

    assert result["canary_probability"] == pytest.approx(0.03)
    assert result["user_class_weights"]


@pytest.mark.asyncio
async def test_get_hydrates_from_db_row():
    from decnet.web.router.realism.api_config import get_config

    stored = json.dumps({"canary_probability": 0.10})
    with patch("decnet.web.router.realism.api_config.repo") as mock_repo:
        mock_repo.get_realism_config = AsyncMock(
            return_value={"key": "weights", "value": stored},
        )
        result = await get_config(user={"uuid": "u", "role": "viewer"})

    assert result["canary_probability"] == pytest.approx(0.10)


@pytest.mark.asyncio
async def test_get_serves_defaults_when_stored_payload_invalid():
    """Stored JSON parsed but failed planner validation: log + serve
    defaults rather than 500."""
    from decnet.web.router.realism.api_config import get_config

    stored = json.dumps({"canary_probability": 9.0})
    with patch("decnet.web.router.realism.api_config.repo") as mock_repo:
        mock_repo.get_realism_config = AsyncMock(
            return_value={"key": "weights", "value": stored},
        )
        result = await get_config(user={"uuid": "u", "role": "viewer"})

    assert result["canary_probability"] == pytest.approx(0.03)


@pytest.mark.asyncio
async def test_put_persists_and_returns_snapshot():
    from decnet.web.router.realism.api_config import put_config

    with patch("decnet.web.router.realism.api_config.repo") as mock_repo:
        mock_repo.set_realism_config = AsyncMock()

        result = await put_config(
            body={"canary_probability": 0.20},
            user={"uuid": "u", "role": "admin", "username": "anti"},
        )

    assert result["canary_probability"] == pytest.approx(0.20)
    mock_repo.set_realism_config.assert_awaited_once()
    args, _ = mock_repo.set_realism_config.call_args
    assert args[0] == "weights"
    persisted = json.loads(args[1])
    assert persisted["canary_probability"] == pytest.approx(0.20)


@pytest.mark.asyncio
async def test_put_returns_400_on_invalid_payload():
    from decnet.web.router.realism.api_config import put_config

    with patch("decnet.web.router.realism.api_config.repo") as mock_repo:
        mock_repo.set_realism_config = AsyncMock()

        with pytest.raises(HTTPException) as exc:
            await put_config(
                body={"canary_probability": 9.0},
                user={"uuid": "u", "role": "admin", "username": "anti"},
            )

    assert exc.value.status_code == 400
    # No DB write on validation failure.
    mock_repo.set_realism_config.assert_not_called()


@pytest.mark.asyncio
async def test_put_rejects_non_dict_body():
    from decnet.web.router.realism.api_config import put_config

    with pytest.raises(HTTPException) as exc:
        await put_config(
            body=[1, 2, 3],  # type: ignore[arg-type]
            user={"uuid": "u", "role": "admin", "username": "anti"},
        )
    assert exc.value.status_code == 400
