"""Real-Postgres test that the CHECK actually accepts 'spike' (0091).

Rule 36 exists because the value and the constraint can drift apart: the
code starts writing a new kind while the database still refuses it, and
nothing catches it until a write fails in front of someone. A mock cannot
show that — it has no CHECK — so the constraint gets its own real-DB test,
the same way migration 0090's nullability did.

The negative half matters as much as the positive one. A test that only
proves 'spike' is accepted would also pass against a table with NO
constraint at all, which is the other way this goes wrong.
"""
import pytest
import pytest_asyncio
from sqlalchemy.exc import IntegrityError

from scribe.models import async_session
from scribe.models.note import Note
from tests.helpers import ensure_user

pytestmark = [pytest.mark.integration, pytest.mark.usefixtures("_dispose_engine")]


@pytest_asyncio.fixture
async def owner_id():
    async with async_session() as s:
        owner = await ensure_user(s, "spike_owner")
        uid = owner.id
        await s.commit()
    return uid


async def _write(uid: int, kind: str) -> int:
    async with async_session() as s:
        # No is_task=: it is a derived read-only property (a note IS a task
        # when status is not None), so passing it raises rather than being
        # ignored. status="todo" is what makes this a task.
        note = Note(
            user_id=uid, title=f"kind {kind}", body="",
            status="todo", task_kind=kind,
        )
        s.add(note)
        await s.commit()
        return note.id


async def test_a_spike_can_be_written(owner_id):
    note_id = await _write(owner_id, "spike")
    async with async_session() as s:
        assert (await s.get(Note, note_id)).task_kind == "spike"


async def test_the_older_kinds_still_write(owner_id):
    """0091 widens the whitelist; it must not narrow it by accident.

    'plan' is retired — plans are milestones since 0066 — but historical
    plan-tasks still carry it, and a row that cannot be rewritten is a row
    that cannot be edited, restored, or migrated.
    """
    for kind in ("work", "issue", "plan"):
        note_id = await _write(owner_id, kind)
        async with async_session() as s:
            assert (await s.get(Note, note_id)).task_kind == kind


async def test_an_unknown_kind_is_still_refused(owner_id):
    """The half that proves a constraint is there at all.

    Without this, every assertion above would pass just as happily against a
    table whose CHECK had been dropped and never re-added — which is exactly
    the failure rule 36 is written against.
    """
    with pytest.raises(IntegrityError):
        await _write(owner_id, "investigation")


async def test_a_kind_can_be_corrected_after_the_fact(owner_id):
    """The write-once bug, asserted against a real column (#3129).

    The read-back is the whole test. A version that only asserted the update
    call succeeded would have passed against the broken code — the route
    returned 200 while dropping the field, which is exactly how this survived
    long enough to be found by hand.
    """
    from scribe.services import notes as notes_svc

    note_id = await _write(owner_id, "work")
    await notes_svc.update_note(owner_id, note_id, task_kind="spike")
    async with async_session() as s:
        assert (await s.get(Note, note_id)).task_kind == "spike"


async def test_an_invalid_kind_is_refused_before_the_database(owner_id):
    """A readable ValueError, not a CheckViolationError surfacing as a 500."""
    from scribe.services import notes as notes_svc

    note_id = await _write(owner_id, "work")
    with pytest.raises(ValueError, match="Invalid kind"):
        await notes_svc.update_note(owner_id, note_id, task_kind="investigation")
    async with async_session() as s:
        assert (await s.get(Note, note_id)).task_kind == "work"
