"""The Interaction Standard in caddie (04 local-only): one thread per stage,
server-side mentions, the other side always hears a reply, dedupe, and the
one bell — plus the §5 event shape the shared inbox will one day serve."""

import itertools

from tests.conftest import as_user

_n = itertools.count(1)


def _project(client, agency):
    as_user(client, agency)
    r = client.post("/api/projects", json={"display_name": f"Ix Project {next(_n)}",
                                           "template_key": "website_build_v1"})
    pid = r.json()["id"]
    stages = client.get(f"/api/projects/{pid}").json()["stages"]
    return pid, stages[0]["id"], stages[0]["title"]


def test_a_mention_rings_the_mentioned_person_and_nobody_invents_it(client, kit, agency):
    pid, sid, title = _project(client, agency)
    kit.member("cleo", "member")
    kit.grant("cleo", pid, "member")

    # mentions are parsed SERVER-side from the stored body — a crafted payload
    # must not be able to page someone who was never named
    r = client.post(f"/api/projects/{pid}/stages/{sid}/comments",
                    json={"body": "@cleo could you confirm the logo files?",
                          "recipients": ["someone_else"]})
    assert r.status_code == 201

    as_user(client, "cleo")
    box = client.get("/api/notifications").json()
    assert box["unread"] == 1 and box["needs_you"] is True
    n = box["notifications"][0]
    assert n["kind"] == "mention" and n["actor"] == agency
    # the label is a breadcrumb in caddie's nouns — "{project} › {stage}" (04 §6)
    assert n["context_label"].endswith(f" › {title}") and n["project_id"] == pid
    # the §5 contract: absolute url, and the app named — one bell, four apps
    assert n["url"].startswith("https://") and f"/projects/{pid}" in n["url"]
    assert n["app"] == "caddie"

    # nobody else was invented into the recipient list
    as_user(client, agency)
    assert client.get("/api/notifications").json()["unread"] == 0


def test_the_other_side_hears_a_reply_and_the_author_never_does(client, kit, agency):
    pid, sid, _ = _project(client, agency)
    kit.member("cleo", "member")
    kit.grant("cleo", pid, "member")

    as_user(client, "cleo")
    client.post(f"/api/projects/{pid}/stages/{sid}/comments", json={"body": "Question here"})
    # the client's own comment must not ring the client
    assert client.get("/api/notifications").json()["unread"] == 0

    as_user(client, agency)
    box = client.get("/api/notifications").json()
    assert box["unread"] == 1 and box["notifications"][0]["kind"] == "reply"


def test_emits_are_deduped(client, kit, agency):
    """A retry or a double-click must not produce two bells."""
    pid, sid, _ = _project(client, agency)
    kit.member("cleo", "member")
    kit.grant("cleo", pid, "member")
    for _ in range(2):
        client.post(f"/api/projects/{pid}/stages/{sid}/status", json={"status": "active"})
    as_user(client, "cleo")
    assert client.get("/api/notifications").json()["unread"] == 1


def test_a_client_cannot_read_or_comment_on_an_internal_stage(client, kit, agency, session):
    pid, sid, _ = _project(client, agency)
    from app.models import Stage
    st = session.get(Stage, sid)
    st.client_visible = False
    session.commit()

    kit.member("cleo", "member")
    kit.grant("cleo", pid, "member")
    as_user(client, "cleo")
    assert client.get(f"/api/projects/{pid}/stages/{sid}/comments").status_code == 404
    assert client.post(f"/api/projects/{pid}/stages/{sid}/comments",
                       json={"body": "hi"}).status_code == 404


def test_approval_rings_the_agency_and_marking_read_clears_it(client, kit, agency):
    pid, _, _ = _project(client, agency)
    stages = client.get(f"/api/projects/{pid}").json()["stages"]
    mockups = next(s for s in stages if s["requires_approval"])
    client.post(f"/api/projects/{pid}/stages/{mockups['id']}/status", json={"status": "active"})

    kit.member("cleo", "member")
    kit.grant("cleo", pid, "member")
    as_user(client, "cleo")
    client.post(f"/api/projects/{pid}/stages/{mockups['id']}/approve")

    as_user(client, agency)
    box = client.get("/api/notifications").json()
    # an approval is a DECISION (04 §5): it closes a loop, so it is news that
    # reads as decided — never "needs you"
    assert any(n["kind"] == "decision" and n["category"] == "stage"
               for n in box["notifications"])
    assert box["needs_you"] is False
    client.post("/api/notifications/read")
    assert client.get("/api/notifications").json()["unread"] == 0


def test_one_thread_per_stage(client, kit, agency, session):
    pid, sid, _ = _project(client, agency)
    for i in range(3):
        client.post(f"/api/projects/{pid}/stages/{sid}/comments", json={"body": f"note {i}"})
    from app.models import Thread
    assert session.query(Thread).filter_by(subject_type="stage", subject_id=sid).count() == 1
    assert len(client.get(f"/api/projects/{pid}/stages/{sid}/comments").json()["comments"]) == 3
