"""v0.29.0 / roadmap #28 Parts 2 & 3 — create-RFC offers + contribute-to-
pending requests.

Two layers, mirroring test_rfc_links_vertical.py:

  * The PR-view scanner surfaces `rfc-pending` (Part 3) and `rfc-candidate`
    (Part 2) segments alongside Part 1's `rfc` links.
  * The contribute-request flow end-to-end: a non-owner asks, each owner
    gets an actionable §15 notification, accept fires #12's invite flow,
    decline notifies the requester.

Reuses the FakeGitea + seed/session helpers from the existing suites.
"""
from __future__ import annotations

import json

from fastapi.testclient import TestClient

from app import db

from test_propose_vertical import (  # noqa: F401
    FakeGitea,
    app_with_fake_gitea,
    provision_user_row,
    sign_in_as,
    tmp_env,
)
from test_rfc_view_vertical import SEED_BODY, seed_active_rfc
from test_super_draft_vertical import seed_super_draft
from test_rfc_links_vertical import _open_pr_on


def _set_owner(slug: str, login: str) -> None:
    db.conn().execute(
        "UPDATE cached_rfcs SET owners_json = ? WHERE slug = ?",
        (json.dumps([login]), slug),
    )


def _set_tags(slug: str, tags: list[str]) -> None:
    db.conn().execute(
        "UPDATE cached_rfcs SET tags_json = ? WHERE slug = ?",
        (json.dumps(tags), slug),
    )


def _segs(segments, kind):
    return [s for s in segments if s["type"] == kind]


# ---------------------------------------------------------------------------
# Part 2 + Part 3 — scanner surfaces on the PR view
# ---------------------------------------------------------------------------


def test_pending_and_candidate_segments_on_pr(app_with_fake_gitea):
    app, fake = app_with_fake_gitea
    with TestClient(app) as client:
        provision_user_row(user_id=2, login="alice", role="contributor")
        # Host active RFC (OHM — single word, contributes no keys itself)
        # carrying a multi-word tag with no defining RFC: the Part 2
        # candidate. And a pending super-draft owned by alice: the Part 3
        # contribute target.
        seed_active_rfc(fake, slug="ohm", title="OHM", body=SEED_BODY)
        _set_tags("ohm", ["memory model", "identity"])
        seed_super_draft(fake, slug="open-human-model", title="Open Human Model",
                         pitch="A framework for representing humans.", proposed_by="alice")
        _set_owner("open-human-model", "alice")
        sign_in_as(client, user_id=2, gitea_login="alice", display_name="Alice", role="contributor")

        pr_number = _open_pr_on(
            client, fake, host_slug="ohm",
            description="This builds on the Open Human Model and the memory model.",
        )
        pr = client.get(f"/api/rfcs/ohm/prs/{pr_number}").json()
        segs = pr["description_segments"]

        pending = _segs(segs, "rfc-pending")
        assert len(pending) == 1
        assert pending[0]["slug"] == "open-human-model"
        assert pending[0]["label"] == "Open Human Model"
        assert pending[0]["owner"] == "Alice"   # display_name of the owner

        candidate = _segs(segs, "rfc-candidate")
        assert len(candidate) == 1
        assert candidate[0]["term"] == "memory model"
        # "identity" is a single-word tag — deliberately NOT a candidate.
        assert all("identity" not in s.get("term", "") for s in candidate)


# ---------------------------------------------------------------------------
# Part 3 — the contribute-request flow
# ---------------------------------------------------------------------------


def _seed_pending_owned_by_alice(fake):
    provision_user_row(user_id=2, login="alice", role="contributor")
    provision_user_row(user_id=3, login="bob", role="contributor")
    seed_super_draft(fake, slug="open-human-model", title="Open Human Model",
                     pitch="A framework.", proposed_by="alice")
    _set_owner("open-human-model", "alice")


_REQUEST = {
    "matched_term": "Open Human Model",
    "who_i_am": "Bob, a researcher",
    "why": "I have relevant prior work to bring.",
    "use_case": "Building an identity tool.",
}


def test_request_accept_invites_and_notifies(app_with_fake_gitea):
    app, fake = app_with_fake_gitea
    with TestClient(app) as client:
        _seed_pending_owned_by_alice(fake)

        # Bob asks to contribute.
        sign_in_as(client, user_id=3, gitea_login="bob", display_name="Bob",
                   role="contributor", email="bob@test")
        r = client.post("/api/rfcs/open-human-model/contribution-requests", json=_REQUEST)
        assert r.status_code == 200, r.text
        request_id = r.json()["id"]
        assert r.json()["status"] == "pending"

        # A second ask while pending is a 409, not a duplicate row.
        assert client.post("/api/rfcs/open-human-model/contribution-requests",
                           json=_REQUEST).status_code == 409

        # Alice (owner) sees the actionable notification with full detail.
        sign_in_as(client, user_id=2, gitea_login="alice", display_name="Alice",
                   role="contributor", email="alice@test")
        inbox = client.get("/api/notifications").json()
        reqs = [i for i in inbox["items"]
                if i["event_kind"] == "contribution_request_on_pending_rfc"]
        assert len(reqs) == 1
        assert "wants to contribute" in reqs[0]["summary"]
        assert reqs[0]["extras"]["who_i_am"] == "Bob, a researcher"
        assert reqs[0]["extras"]["request_id"] == request_id

        # Alice accepts → #12 invitation minted for bob's email.
        acc = client.post(f"/api/rfcs/open-human-model/contribution-requests/{request_id}/accept")
        assert acc.status_code == 200, acc.text
        assert acc.json()["status"] == "accepted"
        assert acc.json()["invitation_id"]

        inv = db.conn().execute(
            "SELECT invitee_email, role_in_rfc, status FROM rfc_invitations "
            "WHERE rfc_slug = 'open-human-model'"
        ).fetchone()
        assert inv["invitee_email"] == "bob@test"
        assert inv["role_in_rfc"] == "contributor"
        assert inv["status"] == "pending"

        # The request is settled — re-accepting is a 409.
        assert client.post(
            f"/api/rfcs/open-human-model/contribution-requests/{request_id}/accept"
        ).status_code == 409

        # Bob gets the accepted echo in his inbox.
        sign_in_as(client, user_id=3, gitea_login="bob", display_name="Bob",
                   role="contributor", email="bob@test")
        bob_kinds = [i["event_kind"] for i in client.get("/api/notifications").json()["items"]]
        assert "contribution_request_accepted" in bob_kinds


def test_decline_notifies_requester(app_with_fake_gitea):
    app, fake = app_with_fake_gitea
    with TestClient(app) as client:
        _seed_pending_owned_by_alice(fake)
        sign_in_as(client, user_id=3, gitea_login="bob", display_name="Bob",
                   role="contributor", email="bob@test")
        request_id = client.post(
            "/api/rfcs/open-human-model/contribution-requests", json=_REQUEST
        ).json()["id"]

        sign_in_as(client, user_id=2, gitea_login="alice", display_name="Alice",
                   role="contributor", email="alice@test")
        dec = client.post(f"/api/rfcs/open-human-model/contribution-requests/{request_id}/decline")
        assert dec.status_code == 200, dec.text
        assert dec.json()["status"] == "declined"

        row = db.conn().execute(
            "SELECT status FROM contribution_requests WHERE id = ?", (request_id,)
        ).fetchone()
        assert row["status"] == "declined"

        sign_in_as(client, user_id=3, gitea_login="bob", display_name="Bob",
                   role="contributor", email="bob@test")
        bob_kinds = [i["event_kind"] for i in client.get("/api/notifications").json()["items"]]
        assert "contribution_request_declined" in bob_kinds


def test_owner_cannot_request_own_rfc(app_with_fake_gitea):
    app, fake = app_with_fake_gitea
    with TestClient(app) as client:
        _seed_pending_owned_by_alice(fake)
        sign_in_as(client, user_id=2, gitea_login="alice", display_name="Alice",
                   role="contributor", email="alice@test")
        r = client.post("/api/rfcs/open-human-model/contribution-requests", json=_REQUEST)
        assert r.status_code == 409
        assert "own" in r.json()["detail"].lower()


def test_request_on_active_rfc_rejected(app_with_fake_gitea):
    # The contribute offer only exists for pending super-drafts; an active
    # RFC uses the Part-1 link instead.
    app, fake = app_with_fake_gitea
    with TestClient(app) as client:
        provision_user_row(user_id=3, login="bob", role="contributor")
        seed_active_rfc(fake, slug="ohm", title="OHM", body=SEED_BODY)
        sign_in_as(client, user_id=3, gitea_login="bob", display_name="Bob",
                   role="contributor", email="bob@test")
        r = client.post("/api/rfcs/ohm/contribution-requests", json=_REQUEST)
        assert r.status_code == 409


def test_contribution_target_eligibility(app_with_fake_gitea):
    app, fake = app_with_fake_gitea
    with TestClient(app) as client:
        _seed_pending_owned_by_alice(fake)

        # Anonymous: not eligible, told to sign in.
        t = client.get("/api/rfcs/open-human-model/contribution-target").json()
        assert t["eligible"] is False
        assert "sign in" in (t["reason"] or "").lower()
        assert t["owner"] == "Alice"

        # Bob: eligible until he has a pending ask, then not.
        sign_in_as(client, user_id=3, gitea_login="bob", display_name="Bob",
                   role="contributor", email="bob@test")
        assert client.get("/api/rfcs/open-human-model/contribution-target").json()["eligible"] is True
        client.post("/api/rfcs/open-human-model/contribution-requests", json=_REQUEST)
        after = client.get("/api/rfcs/open-human-model/contribution-target").json()
        assert after["already_requested"] is True
        assert after["eligible"] is False
