"""/api/saved-views HTTP contract + auth (M8; ADR #009).

Runs on the migrated reporting scratch DB via the routers harness
(real rian/adi actors). Each test cleans up the views it creates — the
scratch DB is session-shared.
"""

import pytest

from tests.reporting.conftest import rpt_engine, rpt_url  # noqa: F401
from tests.routers.conftest import as_adi, as_rian, client_factory  # noqa: F401


def _delete_all(client, page=None):
    q = f"?page={page}" if page else ""
    for v in client.get(f"/api/saved-views{q}").json()["views"]:
        client.delete(f"/api/saved-views/{v['id']}")


@pytest.fixture()
def clean_rian(as_rian):  # noqa: F811
    _delete_all(as_rian)
    yield as_rian
    _delete_all(as_rian)


def test_anon_is_401(client_factory):  # noqa: F811
    # A client with no session cookie: default-deny gate answers before
    # the handler runs.
    from fastapi.testclient import TestClient

    from app.main import create_app

    anon = TestClient(create_app(), base_url="https://testserver")
    with anon:
        assert anon.get("/api/saved-views").status_code == 401
        assert (
            anon.post("/api/saved-views", json={"page": "entries", "name": "x"}).status_code
            == 401
        )


def test_create_list_patch_delete_round_trip(clean_rian):
    c = clean_rian
    # create
    resp = c.post(
        "/api/saved-views",
        json={
            "page": "entries",
            "name": "June PlusROI",
            "search": "date=last-month&operator=PlusROI",
            "pinned": True,
        },
    )
    assert resp.status_code == 200, resp.text
    view = resp.json()["view"]
    assert view["name"] == "June PlusROI"
    assert view["pinned"] is True
    vid = view["id"]

    # list
    listed = c.get("/api/saved-views?page=entries").json()["views"]
    assert [v["id"] for v in listed] == [vid]

    # patch (rename + unpin)
    patched = c.patch(
        f"/api/saved-views/{vid}", json={"name": "June PlusROI (renamed)", "pinned": False}
    )
    assert patched.status_code == 200
    assert patched.json()["view"]["name"] == "June PlusROI (renamed)"
    assert patched.json()["view"]["pinned"] is False
    # search preserved (payload-absent guard)
    assert patched.json()["view"]["search"] == "date=last-month&operator=PlusROI"

    # delete
    assert c.delete(f"/api/saved-views/{vid}").status_code == 200
    assert c.get("/api/saved-views?page=entries").json()["views"] == []


def test_duplicate_name_returns_409(clean_rian):
    c = clean_rian
    c.post("/api/saved-views", json={"page": "entries", "name": "Dup", "search": ""})
    resp = c.post(
        "/api/saved-views", json={"page": "entries", "name": "dup", "search": ""}
    )
    assert resp.status_code == 409
    assert resp.json()["error_code"] == "VIEW_NAME_EXISTS"


def test_bad_page_returns_422(clean_rian):
    resp = clean_rian.post(
        "/api/saved-views", json={"page": "nope", "name": "x", "search": ""}
    )
    assert resp.status_code == 422
    assert resp.json()["error_code"] == "BAD_VIEW_PAGE"


def test_views_are_per_user(as_rian, as_adi):  # noqa: F811
    _delete_all(as_rian)
    _delete_all(as_adi)
    try:
        made = as_rian.post(
            "/api/saved-views", json={"page": "entries", "name": "Rian secret", "search": ""}
        ).json()["view"]
        # adi sees nothing of rian's
        assert as_adi.get("/api/saved-views").json()["views"] == []
        # adi cannot patch or delete rian's view -> 404
        assert as_adi.patch(f"/api/saved-views/{made['id']}", json={"name": "x"}).status_code == 404
        assert as_adi.delete(f"/api/saved-views/{made['id']}").status_code == 404
        # rian still has it
        assert len(as_rian.get("/api/saved-views").json()["views"]) == 1
    finally:
        _delete_all(as_rian)
        _delete_all(as_adi)
