"""ORCH-073 FR-3 — merge_pr merges exactly the feature code-PR (base==main).

Covers TC-08..10 / AC-7 / INV-2/INV-4. The actor selects the open PR with
head==branch AND base==main (never an auto docs-PR / foreign base), merges via the
Gitea PR-merge API only (no push/force-push), and is idempotent on an already-merged
code-PR. Gitea HTTP is mocked; never-raise -> (False, reason).
"""

import httpx
import pytest

from src import merge_gate

BRANCH = "feature/ORCH-073-x"


class _Resp:
    def __init__(self, status_code, payload=None, text=""):
        self.status_code = status_code
        self._payload = payload if payload is not None else []
        self.text = text

    def json(self):
        return self._payload


@pytest.fixture(autouse=True)
def _settings(monkeypatch):
    monkeypatch.setattr(merge_gate.settings, "gitea_url", "http://gitea.test")
    monkeypatch.setattr(merge_gate.settings, "gitea_owner", "admin")
    monkeypatch.setattr(merge_gate.settings, "gitea_token", "tok")
    monkeypatch.setattr(merge_gate.settings, "merge_pr_timeout_s", 5)


# ---------------------------------------------------------------------------
# TC-08: open code-PR (head==branch, base==main) -> POST /pulls/{n}/merge.
# A concurrently-open docs-PR (head=docs/*) must be skipped.
# ---------------------------------------------------------------------------
def test_tc08_merges_code_pr_not_docs_pr(monkeypatch):
    monkeypatch.setattr(merge_gate, "pr_already_merged", lambda r, b: False)
    post_calls = []

    def fake_get(url, params=None, headers=None, timeout=None):
        return _Resp(200, [
            {"head": {"ref": "docs/ORCH-073-log"}, "base": {"ref": "main"}, "number": 4},
            {"head": {"ref": BRANCH}, "base": {"ref": "main"}, "number": 7},
        ])

    def fake_post(url, json=None, headers=None, timeout=None):
        post_calls.append((url, json))
        return _Resp(200)

    monkeypatch.setattr(httpx, "get", fake_get)
    monkeypatch.setattr(httpx, "post", fake_post)

    ok, msg = merge_gate.merge_pr("orchestrator", BRANCH)
    assert ok is True and "PR #7" in msg
    assert len(post_calls) == 1
    url, body = post_calls[0]
    assert url.endswith("/repos/admin/orchestrator/pulls/7/merge")
    assert body == {"Do": "merge"}


def test_tc08_skips_pr_onto_non_main_base(monkeypatch):
    # Right head but base != main -> not a merge-to-main code-PR -> no open PR.
    monkeypatch.setattr(merge_gate, "pr_already_merged", lambda r, b: False)
    monkeypatch.setattr(
        httpx, "get",
        lambda *a, **k: _Resp(200, [{"head": {"ref": BRANCH}, "base": {"ref": "develop"}, "number": 9}]),
    )
    monkeypatch.setattr(httpx, "post", lambda *a, **k: (_ for _ in ()).throw(
        AssertionError("must not POST merge for a non-main base PR")))
    ok, msg = merge_gate.merge_pr("orchestrator", BRANCH)
    assert ok is False and msg == "no open PR"


# ---------------------------------------------------------------------------
# TC-09 (INV-2): no open code-PR -> (False, "no open PR"); never shells out.
# ---------------------------------------------------------------------------
def test_tc09_no_open_pr_no_shell_out(monkeypatch):
    monkeypatch.setattr(merge_gate, "pr_already_merged", lambda r, b: False)
    monkeypatch.setattr(httpx, "get", lambda *a, **k: _Resp(200, []))
    subprocess_calls = []
    monkeypatch.setattr(
        merge_gate.subprocess, "run",
        lambda cmd, *a, **k: subprocess_calls.append(cmd),
    )
    ok, msg = merge_gate.merge_pr("orchestrator", BRANCH)
    assert ok is False and msg == "no open PR"
    # No git push/force-push (or any subprocess) for the merge-actor.
    assert subprocess_calls == []


# ---------------------------------------------------------------------------
# TC-10 (AC-7/INV-4): already-merged code-PR -> no-op, no second POST merge.
# ---------------------------------------------------------------------------
def test_tc10_idempotent_already_merged(monkeypatch):
    monkeypatch.setattr(merge_gate, "pr_already_merged", lambda r, b: True)

    def must_not_call(*a, **k):
        raise AssertionError("no Gitea call when the code-PR is already merged")

    monkeypatch.setattr(httpx, "get", must_not_call)
    monkeypatch.setattr(httpx, "post", must_not_call)
    ok, msg = merge_gate.merge_pr("orchestrator", BRANCH)
    assert ok is True and msg == "already-merged"
