"""End-to-end integration tests for the v0.5.0 PR-less discussion
surface — roadmap item #3, "discussion without PR; contribution requires
PR."

The vertical: an active RFC exists; the discussion endpoints under
`/api/rfcs/<slug>/discussion/...` open threads with
`threads.branch_name IS NULL`, post messages into them, and surface
them on subsequent reads. Branch-scoped threads (the §8.12 surface)
remain segregated. Anonymous viewers can read; only signed-in
contributors can write.
"""
from __future__ import annotations

import pytest

# Reuse the harness from Slice 1 / Slice 2.
from test_propose_vertical import (  # noqa: F401 — fixtures land via import
    FakeGitea,
    app_with_fake_gitea,
    provision_user_row,
    sign_in_as,
    tmp_env,
)
from test_rfc_view_vertical import seed_active_rfc, SEED_BODY


# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------


def test_create_and_post_to_pr_less_discussion_thread(app_with_fake_gitea):
    """The vertical: signed-in contributor opens a thread on the RFC's
    discussion surface, posts a message, and the thread + message
    surface on subsequent reads with branch_name IS NULL."""
    from fastapi.testclient import TestClient
    from app import db

    app, fake = app_with_fake_gitea
    with TestClient(app) as client:
        provision_user_row(user_id=1, login="alice", role="contributor")
        seed_active_rfc(fake, slug="ohm", title="OHM", body=SEED_BODY)
        sign_in_as(client, user_id=1, gitea_login="alice", display_name="Alice", role="contributor")

        # Listing materializes the default whole-doc thread.
        r = client.get("/api/rfcs/ohm/discussion/threads")
        assert r.status_code == 200, r.text
        items = r.json()["items"]
        assert len(items) == 1
        default_thread_id = items[0]["id"]
        assert items[0]["anchor_kind"] == "whole-doc"
        assert items[0]["thread_kind"] == "chat"

        # Open an additional discussion thread with a first message.
        r = client.post(
            "/api/rfcs/ohm/discussion/threads",
            json={"label": "Question about §3", "message": "Is consent baked into the trait model?"},
        )
        assert r.status_code == 200, r.text
        payload = r.json()
        thread_id = payload["thread_id"]
        message_id = payload["message_id"]
        assert thread_id is not None and message_id is not None

        # Confirm the row carries branch_name IS NULL (the PR-less shape).
        row = db.conn().execute(
            "SELECT rfc_slug, branch_name, thread_kind, anchor_kind, created_by FROM threads WHERE id = ?",
            (thread_id,),
        ).fetchone()
        assert row["rfc_slug"] == "ohm"
        assert row["branch_name"] is None
        assert row["thread_kind"] == "chat"
        assert row["anchor_kind"] == "whole-doc"
        assert row["created_by"] == 1

        # The thread surfaces on the list endpoint alongside the default.
        r = client.get("/api/rfcs/ohm/discussion/threads")
        ids = [t["id"] for t in r.json()["items"]]
        assert default_thread_id in ids
        assert thread_id in ids

        # Posting a reply on the new thread persists and returns the id.
        r = client.post(
            f"/api/rfcs/ohm/discussion/threads/{thread_id}/messages",
            json={"text": "Following up — see §3.2."},
        )
        assert r.status_code == 200, r.text
        reply_id = r.json()["message_id"]

        # The messages read endpoint returns both messages in order.
        r = client.get(f"/api/rfcs/ohm/discussion/threads/{thread_id}/messages")
        assert r.status_code == 200
        messages = r.json()["messages"]
        assert [m["id"] for m in messages] == [message_id, reply_id]
        assert messages[0]["author_login"] == "alice"
        assert messages[0]["text"].startswith("Is consent")


def test_anonymous_can_read_but_cannot_post_discussion(app_with_fake_gitea):
    """Per the v0.3.0 anonymous-read contract: reads on the discussion
    surface are open; write attempts return 401. v0.6.0 (item #4) will
    tighten the read gate — v0.5.0 holds the write line so there is no
    open window between releases."""
    from fastapi.testclient import TestClient

    app, fake = app_with_fake_gitea
    with TestClient(app) as client:
        provision_user_row(user_id=2, login="alice", role="contributor")
        seed_active_rfc(fake, slug="ohm", title="OHM", body=SEED_BODY)

        # Seed the discussion thread + first message as Alice.
        sign_in_as(client, user_id=2, gitea_login="alice", display_name="Alice", role="contributor")
        r = client.post(
            "/api/rfcs/ohm/discussion/threads",
            json={"message": "First."},
        )
        assert r.status_code == 200
        thread_id = r.json()["thread_id"]

        # Drop the session — viewer is anonymous now.
        client.cookies.clear()

        # Reads are open.
        r = client.get("/api/rfcs/ohm/discussion/threads")
        assert r.status_code == 200
        assert any(t["id"] == thread_id for t in r.json()["items"])

        r = client.get(f"/api/rfcs/ohm/discussion/threads/{thread_id}/messages")
        assert r.status_code == 200
        assert len(r.json()["messages"]) >= 1

        # Writes refuse 401.
        r = client.post(
            "/api/rfcs/ohm/discussion/threads",
            json={"message": "Drive-by."},
        )
        assert r.status_code == 401

        r = client.post(
            f"/api/rfcs/ohm/discussion/threads/{thread_id}/messages",
            json={"text": "Drive-by reply."},
        )
        assert r.status_code == 401


def test_discussion_threads_and_branch_threads_are_segregated(app_with_fake_gitea):
    """A branch-scoped thread (the §8.12 surface, branch_name='main' or a
    feature branch) MUST NOT surface on the discussion endpoint, which
    is keyed on branch_name IS NULL. The two surfaces share a table; the
    null-filter is what segregates them."""
    from fastapi.testclient import TestClient
    from app import db

    app, fake = app_with_fake_gitea
    with TestClient(app) as client:
        provision_user_row(user_id=3, login="alice", role="contributor")
        seed_active_rfc(fake, slug="ohm", title="OHM", body=SEED_BODY)
        sign_in_as(client, user_id=3, gitea_login="alice", display_name="Alice", role="contributor")

        # Manually materialize a branch-scoped thread on a feature branch.
        db.conn().execute(
            """
            INSERT INTO threads
                (rfc_slug, branch_name, anchor_kind, thread_kind, label, created_by)
            VALUES ('ohm', 'alice-draft-aa00', 'whole-doc', 'chat', NULL, 3)
            """
        )
        # And one on the discussion surface.
        r = client.post(
            "/api/rfcs/ohm/discussion/threads",
            json={"message": "Discussion-surface message."},
        )
        assert r.status_code == 200
        discussion_thread_id = r.json()["thread_id"]

        # The discussion list contains the null-branch thread (plus the
        # default whole-doc) and excludes the feature-branch thread.
        r = client.get("/api/rfcs/ohm/discussion/threads")
        assert r.status_code == 200
        ids = [t["id"] for t in r.json()["items"]]
        assert discussion_thread_id in ids
        # Feature-branch thread MUST NOT surface.
        branch_thread_row = db.conn().execute(
            "SELECT id FROM threads WHERE branch_name = 'alice-draft-aa00'"
        ).fetchone()
        assert branch_thread_row is not None
        assert branch_thread_row["id"] not in ids


def test_discussion_thread_resolve_permissions(app_with_fake_gitea):
    """A thread's creator can resolve it; an unrelated contributor cannot;
    an admin / owner / RFC-owner can. Mirrors §8.12's resolution rule for
    branch-scoped threads."""
    from fastapi.testclient import TestClient

    app, fake = app_with_fake_gitea
    with TestClient(app) as client:
        provision_user_row(user_id=4, login="alice", role="contributor")
        provision_user_row(user_id=5, login="bob", role="contributor")
        provision_user_row(user_id=6, login="ben", role="owner")
        seed_active_rfc(fake, slug="ohm", title="OHM", body=SEED_BODY)

        sign_in_as(client, user_id=4, gitea_login="alice", display_name="Alice", role="contributor")
        r = client.post(
            "/api/rfcs/ohm/discussion/threads",
            json={"label": "Alice's thread", "message": "..."},
        )
        thread_id = r.json()["thread_id"]

        # Unrelated contributor refused.
        sign_in_as(client, user_id=5, gitea_login="bob", display_name="Bob", role="contributor")
        r = client.post(f"/api/rfcs/ohm/discussion/threads/{thread_id}/resolve")
        assert r.status_code == 403

        # Creator allowed.
        sign_in_as(client, user_id=4, gitea_login="alice", display_name="Alice", role="contributor")
        r = client.post(f"/api/rfcs/ohm/discussion/threads/{thread_id}/resolve")
        assert r.status_code == 200

        # Open another thread, resolve it as the owner.
        r = client.post(
            "/api/rfcs/ohm/discussion/threads",
            json={"label": "Another thread", "message": "..."},
        )
        thread_id2 = r.json()["thread_id"]
        sign_in_as(client, user_id=6, gitea_login="ben", display_name="Ben", role="owner")
        r = client.post(f"/api/rfcs/ohm/discussion/threads/{thread_id2}/resolve")
        assert r.status_code == 200


def test_discussion_404_on_unknown_rfc(app_with_fake_gitea):
    from fastapi.testclient import TestClient

    app, _fake = app_with_fake_gitea
    with TestClient(app) as client:
        r = client.get("/api/rfcs/nonexistent/discussion/threads")
        assert r.status_code == 404
