"""/api/comments — the global board + bell, over the migrated fixture
graph (rpt scratch DB). Covers 06-test-plan §7.3 T-COM-101…115. Every
test restores the state it touches — the DB is shared, session-scoped.

Migrated baseline (legacy_fixture.sql):
  11..01  author=rian            OPEN      body 'remember to re-check May'
  11..02  author=NULL (ghost)    RESOLVED  body 'old note'  (resolved_by=adi)
"""

import uuid

from sqlalchemy import text

COMMENT_OPEN = "11000000-0000-0000-0000-000000000001"
COMMENT_RESOLVED = "11000000-0000-0000-0000-000000000002"


# ------------------------------------------------------------- helpers


def _board(client) -> dict:
    resp = client.get("/api/comments")
    assert resp.status_code == 200, resp.text
    return resp.json()


def _count(client) -> int:
    return _board(client)["unresolved_count"]


def _post(client, body: str) -> dict:
    resp = client.post("/api/comments", json={"body": body})
    assert resp.status_code == 200, resp.text
    payload = resp.json()
    assert payload["ok"] is True
    return payload["comment"]


def _find(client, comment_id: str) -> dict | None:
    for c in _board(client)["comments"]:
        if c["id"] == comment_id:
            return c
    return None


# ------------------------------------------------------------ baseline


def test_migrated_baseline_present(as_rian):
    """The 2 real migrated comments are readable, correctly attributed,
    and the badge counts only the open one."""
    board = _board(as_rian)
    by_id = {c["id"]: c for c in board["comments"]}
    assert COMMENT_OPEN in by_id and COMMENT_RESOLVED in by_id

    open_c = by_id[COMMENT_OPEN]
    assert open_c["resolved"] is False
    assert open_c["body"] == "remember to re-check May"
    assert open_c["author_name"] not in (None, "", "Unknown")  # rian's party

    resolved_c = by_id[COMMENT_RESOLVED]
    assert resolved_c["resolved"] is True
    # unmapped ghost author migrated to NULL -> "Unknown" (T-COM-110)
    assert resolved_c["author_id"] is None
    assert resolved_c["author_name"] == "Unknown"

    assert board["unresolved_count"] >= 1  # at least the migrated open one


# ------------------------------------------------------------- T-COM-101


def test_com_101_post_visible_to_other_member_newest_first(as_rian, as_adi):
    before = _count(as_adi)
    posted = _post(as_rian, "@Adi please rename PromptVictoria in Clockify")
    try:
        board = _board(as_adi)  # the OTHER member sees it
        assert board["unresolved_count"] == before + 1  # badge increments
        assert board["comments"][0]["id"] == posted["id"]  # newest-first
        assert board["comments"][0]["author_name"] not in (None, "", "Unknown")
        assert board["comments"][0]["resolved"] is False
    finally:
        as_rian.delete(f"/api/comments/{posted['id']}")


# ------------------------------------------------------------- T-COM-102


def test_com_102_any_member_resolves_badge_decrements(as_rian, as_adi):
    before = _count(as_rian)
    posted = _post(as_rian, "check the June export")
    try:
        assert _count(as_rian) == before + 1
        # a NON-author member (adi) may resolve — shared-board semantics
        resolved = as_adi.post(f"/api/comments/{posted['id']}/resolve")
        assert resolved.status_code == 200, resolved.text
        row = resolved.json()["comment"]
        assert row["resolved"] is True
        assert row["resolved_at"] is not None
        assert row["resolved_by"] is not None
        assert row["resolved_by_name"] not in (None, "", "Unknown")  # adi
        assert _count(as_rian) == before  # badge decremented back
    finally:
        as_rian.delete(f"/api/comments/{posted['id']}")


# ------------------------------------------------------------- T-COM-103


def test_com_103_author_and_superadmin_delete_other_member_denied(as_rian, as_adi):
    """Author deletes; super-admin deletes; a non-author non-admin member
    gets an EXPLICIT 403 (legacy silent RLS no-op fixed)."""
    # --- non-author member is DENIED (rian's comment, adi tries) ---
    rian_comment = _post(as_rian, "owned by rian")
    try:
        can = _find(as_adi, rian_comment["id"])
        assert can is not None and can["can_delete"] is False
        denied = as_adi.delete(f"/api/comments/{rian_comment['id']}")
        assert denied.status_code == 403
        assert denied.json()["error_code"] == "NOT_COMMENT_AUTHOR"
        assert _find(as_rian, rian_comment["id"]) is not None  # nothing deleted
    finally:
        as_rian.delete(f"/api/comments/{rian_comment['id']}")

    # --- author deletes their own ---
    adi_own = _post(as_adi, "adi deletes this one")
    assert _find(as_adi, adi_own["id"])["can_delete"] is True
    assert as_adi.delete(f"/api/comments/{adi_own['id']}").status_code == 200
    assert _find(as_adi, adi_own["id"]) is None

    # --- super-admin deletes someone else's ---
    adi_other = _post(as_adi, "rian the super-admin deletes this")
    assert _find(as_rian, adi_other["id"])["can_delete"] is True  # SA can
    assert as_rian.delete(f"/api/comments/{adi_other['id']}").status_code == 200
    assert _find(as_rian, adi_other["id"]) is None


# ------------------------------------------------------------- T-COM-104


def test_com_104_reopen_returns_to_open_and_badge(as_rian, as_adi):
    before = _count(as_rian)
    posted = _post(as_rian, "reopen me")
    try:
        as_rian.post(f"/api/comments/{posted['id']}/resolve")
        assert _count(as_rian) == before  # resolved: off the badge
        reopened = as_adi.post(f"/api/comments/{posted['id']}/reopen")  # any member
        assert reopened.status_code == 200, reopened.text
        row = reopened.json()["comment"]
        assert row["resolved"] is False
        assert row["resolved_at"] is None
        assert row["resolved_by"] is None
        assert _count(as_rian) == before + 1  # back on the badge
    finally:
        as_rian.delete(f"/api/comments/{posted['id']}")


# ------------------------------------------------------------- T-COM-105


def test_com_105_empty_body_rejected_no_row(as_rian):
    before = _count(as_rian)
    for bad in ("", "   ", "\t\n  "):
        resp = as_rian.post("/api/comments", json={"body": bad})
        assert resp.status_code == 422
        assert resp.json()["error_code"] == "BODY_REQUIRED"
        assert resp.json()["summary"] == "Comment body is required."
    assert _count(as_rian) == before  # nothing inserted


# ------------------------------------------------------------- T-COM-106


def test_com_106_badge_counts_all_unresolved_past_window(as_rian):
    """51 unresolved comments: the list window is capped at BELL_WINDOW
    (50), but the badge is a REAL count(*) that includes the one beyond
    the window (legacy undercount fixed)."""
    before = _count(as_rian)
    ids: list[str] = []
    try:
        for i in range(51):
            ids.append(_post(as_rian, f"bulk unresolved {i}")["id"])
        board = _board(as_rian)
        assert board["window"] == 50
        assert len(board["comments"]) == 50  # window caps the LIST
        assert board["unresolved_count"] == before + 51  # count is uncapped
    finally:
        for cid in ids:
            as_rian.delete(f"/api/comments/{cid}")


# ------------------------------------------------------------- T-COM-108


def test_com_108_view_as_attributes_to_effective_user(as_rian, as_adi):
    """A super admin viewing-as Adi posts a comment authored AS Adi
    (the effective user), verified after exiting view-as."""
    # reference identity: what a comment authored by adi looks like
    ref = _post(as_adi, "adi reference")
    adi_user_id, adi_name = ref["author_id"], ref["author_name"]
    as_adi.delete(f"/api/comments/{ref['id']}")
    assert adi_user_id is not None

    enter = as_rian.post("/api/view-as", json={"username": "adi"})
    assert enter.status_code == 200, enter.text
    try:
        posted = _post(as_rian, "posted while viewing-as adi")
        assert posted["author_id"] == adi_user_id
        assert posted["author_name"] == adi_name
    finally:
        as_rian.delete("/api/view-as")
    # after exiting view-as the comment still reads as authored by adi
    seen = _find(as_rian, posted["id"])
    assert seen is not None and seen["author_id"] == adi_user_id
    as_rian.delete(f"/api/comments/{posted['id']}")


# ------------------------------------------------------------- T-COM-109


def test_com_109_view_as_delete_uses_one_identity(as_rian, as_adi):
    """Under view-as, `can_delete` (button visibility) and the delete
    handler (enforcement) agree on ONE identity — the effective user."""
    rian_comment = _post(as_rian, "rian-authored, pre view-as")
    try:
        enter = as_rian.post("/api/view-as", json={"username": "adi"})
        assert enter.status_code == 200, enter.text
        try:
            # while viewing-as adi, post a comment (authored as adi)
            adi_comment = _post(as_rian, "authored while viewing-as adi")

            # button visibility: adi's own -> deletable; rian's -> not
            assert _find(as_rian, adi_comment["id"])["can_delete"] is True
            assert _find(as_rian, rian_comment["id"])["can_delete"] is False

            # enforcement matches EXACTLY: rian's comment refused (SA power
            # stripped while impersonating), adi's own allowed
            refused = as_rian.delete(f"/api/comments/{rian_comment['id']}")
            assert refused.status_code == 403
            assert refused.json()["error_code"] == "NOT_COMMENT_AUTHOR"
            assert as_rian.delete(f"/api/comments/{adi_comment['id']}").status_code == 200
        finally:
            as_rian.delete("/api/view-as")
    finally:
        as_rian.delete(f"/api/comments/{rian_comment['id']}")


# ------------------------------------------------------------- T-COM-110


def test_com_110_deleted_author_renders_unknown(as_rian):
    """The migrated ghost-authored comment renders author 'Unknown'."""
    resolved_c = _find(as_rian, COMMENT_RESOLVED)
    assert resolved_c is not None
    assert resolved_c["author_id"] is None
    assert resolved_c["author_name"] == "Unknown"
    # its resolver (adi) IS mapped, so it is NOT "someone"
    assert resolved_c["resolved_by_name"] not in (None, "", "Unknown")


def test_com_110_nulled_resolver_renders_someone(as_rian, rpt_session):
    """A resolved comment whose resolver account was SET NULL exposes a
    null resolved_by_name — the frontend renders 'Resolved by someone'."""
    posted = _post(as_rian, "resolver will vanish")
    try:
        as_rian.post(f"/api/comments/{posted['id']}/resolve")
        # simulate the ON DELETE SET NULL of the resolver account
        rpt_session.execute(
            text("UPDATE comments SET resolved_by = NULL WHERE id = :id"),
            {"id": posted["id"]},
        )
        rpt_session.commit()
        row = _find(as_rian, posted["id"])
        assert row is not None
        assert row["resolved"] is True
        assert row["resolved_by"] is None
        assert row["resolved_by_name"] is None  # -> "someone" in the UI
    finally:
        as_rian.delete(f"/api/comments/{posted['id']}")


# ------------------------------------------------------------- T-COM-111


def test_com_111_open_resolved_partition_is_derivable(as_rian):
    """The board returns each comment's `resolved` flag so the Open /
    Resolved tabs partition the same window client-side (the empty-state
    copy is asserted in the frontend suite)."""
    posted_open = _post(as_rian, "an open one")
    posted_resolved = _post(as_rian, "a resolved one")
    try:
        as_rian.post(f"/api/comments/{posted_resolved['id']}/resolve")
        board = _board(as_rian)
        by_id = {c["id"]: c for c in board["comments"]}
        assert by_id[posted_open["id"]]["resolved"] is False
        assert by_id[posted_resolved["id"]]["resolved"] is True
        # both migrated states are also partitionable
        assert by_id[COMMENT_OPEN]["resolved"] is False
        assert by_id[COMMENT_RESOLVED]["resolved"] is True
    finally:
        as_rian.delete(f"/api/comments/{posted_open['id']}")
        as_rian.delete(f"/api/comments/{posted_resolved['id']}")


# ------------------------------------------------------------- T-COM-112


def test_com_112_unauthenticated_actions_refused_nothing_written(anon_client, as_rian):
    before = _count(as_rian)
    assert anon_client.get("/api/comments").status_code == 401
    assert anon_client.post("/api/comments", json={"body": "sneaky"}).status_code == 401
    assert anon_client.post(f"/api/comments/{COMMENT_OPEN}/resolve").status_code == 401
    assert anon_client.post(f"/api/comments/{COMMENT_OPEN}/reopen").status_code == 401
    assert anon_client.delete(f"/api/comments/{COMMENT_OPEN}").status_code == 401
    assert _count(as_rian) == before  # nothing changed
    assert _find(as_rian, COMMENT_OPEN)["resolved"] is False  # still open


# ------------------------------------------------------------- T-COM-113


def test_com_113_missing_id_is_not_found_not_silent(as_rian):
    before = _count(as_rian)
    ghost = uuid.uuid4()
    for resp in (
        as_rian.post(f"/api/comments/{ghost}/resolve"),
        as_rian.post(f"/api/comments/{ghost}/reopen"),
        as_rian.delete(f"/api/comments/{ghost}"),
    ):
        assert resp.status_code == 404
        assert resp.json()["error_code"] == "COMMENT_NOT_FOUND"
    assert _count(as_rian) == before  # no state change


# ------------------------------------------------------------- T-COM-115 (B9)


def test_com_115_resolve_restamps_reopen_open_is_noop(as_rian, as_adi):
    """No state precondition: re-resolving an already-resolved comment
    re-stamps resolved_at/resolved_by to the new actor/time; reopening an
    already-open comment is a no-op write (no error)."""
    posted = _post(as_rian, "b9 lifecycle")
    try:
        first = as_adi.post(f"/api/comments/{posted['id']}/resolve").json()["comment"]
        assert first["resolved_by_name"] not in (None, "", "Unknown")  # adi
        first_at, first_by = first["resolved_at"], first["resolved_by"]

        # resolve AGAIN as a different actor -> re-stamps (no precondition)
        second = as_rian.post(f"/api/comments/{posted['id']}/resolve").json()["comment"]
        assert second["resolved_by"] != first_by  # resolver changed to rian
        assert second["resolved_at"] >= first_at  # time re-stamped

        # reopen twice: first clears; second is a no-op (still open, 200)
        assert as_rian.post(f"/api/comments/{posted['id']}/reopen").status_code == 200
        again = as_rian.post(f"/api/comments/{posted['id']}/reopen")
        assert again.status_code == 200
        assert again.json()["comment"]["resolved"] is False
        assert again.json()["comment"]["resolved_at"] is None
    finally:
        as_rian.delete(f"/api/comments/{posted['id']}")
