"""Real-Postgres integration tests for project inception (milestone 297).

What mocks can't prove: a decision's effects land through the real services
(exclusions filter the always-on set, subscriptions bind, the design system
points, the standard Systems seed once), the record is written last, a bad
target applies nothing.
"""
import pytest
import pytest_asyncio

from scribe.models import async_session
from scribe.models.project import Project
from scribe.models.rulebook import Rulebook
from scribe.services import inception as inception_svc
from scribe.services import rulebooks as rulebooks_svc
from scribe.services import systems as systems_svc
from tests.helpers import ensure_user

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


@pytest_asyncio.fixture
async def seeded():
    """Owner, a fresh project, one always-on rulebook (with a rule) and one
    ordinary rulebook (with a rule)."""
    async with async_session() as s:
        owner = await ensure_user(s, "inception_owner")
        project = Project(user_id=owner.id, title="Inception target")
        s.add(project)
        await s.flush()
        ids = {"owner": owner.id, "pid": project.id}
        await s.commit()
    always = await rulebooks_svc.create_rulebook(ids["owner"], "Family standards")
    other = await rulebooks_svc.create_rulebook(ids["owner"], "Optional practices")
    async with async_session() as s:
        rb = await s.get(Rulebook, always.id)
        rb.always_on = True
        await s.commit()
    t1 = await rulebooks_svc.create_topic(always.id, ids["owner"], "git")
    await rulebooks_svc.create_rule(t1.id, ids["owner"], "dev is home", "Work on dev.")
    t2 = await rulebooks_svc.create_topic(other.id, ids["owner"], "docs")
    await rulebooks_svc.create_rule(t2.id, ids["owner"], "Write the why", "Record reasons.")
    ids.update({"always": always.id, "other": other.id})
    return ids


@pytest.mark.integration
async def test_decide_applies_every_effect_and_records_last(seeded):
    owner, pid = seeded["owner"], seeded["pid"]
    # Undecided: the always-on rulebook binds, nothing subscribed, no Systems.
    assert [r.title for r in await rulebooks_svc.list_always_on_rules(owner, project_id=pid)] == ["dev is home"]
    defaults = await inception_svc.current_defaults(owner, pid)
    assert [r["id"] for r in defaults["always_on_rulebooks"]] == [seeded["always"]]
    assert [r["id"] for r in defaults["other_rulebooks"]] == [seeded["other"]]
    assert defaults["systems"] == 0 and defaults["design_system_id"] is None

    out = await inception_svc.decide(owner, pid, via="mcp", choices={
        "exclude_always_on_rulebooks": [seeded["always"]],
        "subscribe_rulebooks": [seeded["other"]],
        "design_system_id": None,
        "seed_systems": True,
    })
    assert out["effects"]["excluded"] == [seeded["always"]]
    assert out["effects"]["subscribed"] == [seeded["other"]]
    assert len(out["effects"]["systems_seeded"]) == len(systems_svc.STANDARD_SYSTEMS)

    # The exclusion is total: the project's always-on set is empty, the
    # departure is named, the subscription binds.
    assert await rulebooks_svc.list_always_on_rules(owner, project_id=pid) == []
    assert len(await rulebooks_svc.list_always_on_rules(owner)) == 1   # user-wide unchanged
    applicable = await rulebooks_svc.get_applicable_rules(pid, owner)
    assert [r["title"] for r in applicable["rules"]] == ["Write the why"]
    assert [e["id"] for e in applicable["excluded_always_on"]] == [seeded["always"]]
    assert [s["id"] for s in applicable["subscribed_rulebooks"]] == [seeded["other"]]
    # The record, written last, says why.
    async with async_session() as s:
        project = await s.get(Project, pid)
    assert inception_svc.is_decided(project)
    assert project.inception["via"] == "mcp" and project.inception["decided_by"] == owner
    assert project.inception["choices"]["exclude_always_on_rulebooks"] == [seeded["always"]]
    # Re-deciding with seed again mints nothing twice; include reverses the exclusion.
    again = await inception_svc.decide(owner, pid, via="ui", choices={"seed_systems": True})
    assert again["effects"]["systems_seeded"] == []
    assert len(await systems_svc.list_systems(owner, pid)) == len(systems_svc.STANDARD_SYSTEMS)
    await rulebooks_svc.include_always_on_rulebook_for_project(pid, seeded["always"], owner)
    assert [r.title for r in await rulebooks_svc.list_always_on_rules(owner, project_id=pid)] == ["dev is home"]


@pytest.mark.integration
async def test_a_bad_decision_applies_nothing(seeded):
    owner, pid = seeded["owner"], seeded["pid"]
    # Excluding a rulebook that is not always-on is refused BEFORE any effect.
    with pytest.raises(ValueError, match="not always-on"):
        await inception_svc.decide(owner, pid, via="mcp", choices={
            "exclude_always_on_rulebooks": [seeded["other"]], "seed_systems": True,
        })
    assert await systems_svc.list_systems(owner, pid) == []
    with pytest.raises(ValueError, match="not found"):
        await inception_svc.decide(owner, pid, via="mcp", choices={"subscribe_rulebooks": [999999]})
    with pytest.raises(ValueError, match="legacy"):
        await inception_svc.decide(owner, pid, via="legacy", choices={})
    async with async_session() as s:
        project = await s.get(Project, pid)
    assert not inception_svc.is_decided(project)
    # An outsider cannot decide someone else's project.
    async with async_session() as s:
        other = await ensure_user(s, "inception_other")
        other_id = other.id
        await s.commit()
    with pytest.raises(ValueError, match="not found"):
        await inception_svc.decide(other_id, pid, via="mcp", choices={})
