"""Tests for #218 — paperclip_client.escalate_issue (loop-safe chair escalation).

Verifies the primitive that replaces the fragile manual PATCH dance:
- invalid severity is rejected before any DB work.
- the happy path performs ONE atomic human-owned transition
  (``status='in_review'``, ``assignee_user_id=CHAIM_USER_ID``) and records a
  ``author_type='system'`` severity note carrying the reason.
- **no wakeup / REST call is issued** — escalation hands off to the human, it
  must never re-invoke an agent (that is what caused the recovery loops).
- a missing issue reports ``ok:False`` and writes no comment.

Uses a fake asyncpg connection — never touches the live Paperclip DB.
"""

from __future__ import annotations

import asyncio
import os
import sys
from pathlib import Path

import pytest

os.environ.setdefault("PAPERCLIP_DB_URL", "postgres://x:x@127.0.0.1:54329/paperclip")
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))  # web/

pc = pytest.importorskip("paperclip_client", reason="web deps unavailable")


class _FakeTxn:
    async def __aenter__(self):
        return None

    async def __aexit__(self, *exc):
        return False


class _FakeConn:
    def __init__(self, row):
        self._row = row
        self.updates: list[tuple] = []
        self.inserts: list[tuple] = []
        self.closed = False

    def transaction(self):
        return _FakeTxn()

    async def fetchrow(self, query, *args):
        self.updates.append((query, args))
        return self._row

    async def execute(self, query, *args):
        self.inserts.append((query, args))

    async def close(self):
        self.closed = True


@pytest.fixture
def fake_db(monkeypatch):
    """Patch asyncpg.connect + guard that no wakeup/REST is issued."""
    conns: list[_FakeConn] = []
    row = {"id": "iss-uuid", "identifier": "CMP-141", "company_id": "cmp-uuid"}
    holder = {"row": row}

    async def _connect(_url):
        conn = _FakeConn(holder["row"])
        conns.append(conn)
        return conn

    monkeypatch.setattr(pc.asyncpg, "connect", _connect)

    async def _forbidden(*a, **k):  # any REST/wakeup would be a loop-risk
        raise AssertionError("escalate_issue must not issue a wakeup/REST call")

    monkeypatch.setattr(pc, "pc_request", _forbidden, raising=False)
    return {"conns": conns, "holder": holder}


def test_invalid_severity_rejected_before_db(fake_db):
    result = asyncio.run(pc.escalate_issue("iss-1", "urgent", "boom"))
    assert result["ok"] is False
    assert "invalid severity" in result["error"]
    assert fake_db["conns"] == []  # never connected


def test_happy_path_atomic_transition_and_system_note(fake_db):
    result = asyncio.run(
        pc.escalate_issue("iss-uuid", "high", "analyst wedged on protocol parse")
    )
    assert result == {
        "ok": True,
        "id": "iss-uuid",
        "identifier": "CMP-141",
        "severity": "high",
        "status": "in_review",
    }
    conn = fake_db["conns"][0]
    # 1) one human-owned UPDATE with the chair user + in_review
    (update_sql, update_args) = conn.updates[0]
    assert "status='in_review'" in update_sql
    assert "assignee_agent_id=null" in update_sql
    assert update_args[0] == pc.CHAIM_USER_ID
    assert update_args[1] == "iss-uuid"
    # 2) a system-authored severity note carrying the reason
    (insert_sql, insert_args) = conn.inserts[0]
    assert "issue_comments" in insert_sql
    assert "'system'" in insert_sql
    assert "high" in insert_args[3] and "wedged on protocol parse" in insert_args[3]
    assert conn.closed is True


def test_missing_issue_reports_not_found_and_writes_no_comment(fake_db):
    fake_db["holder"]["row"] = None
    result = asyncio.run(pc.escalate_issue("ghost", "medium", "nope"))
    assert result["ok"] is False and "not found" in result["error"]
    conn = fake_db["conns"][0]
    assert conn.inserts == []  # no comment on a non-existent issue
