"""ORCH-071 — deterministic merge-actor (merge_gate.merge_pr).

Covers TC-07 (FR-1: merge via Gitea PR-merge API, no push/force-push), TC-08
(AC-9: idempotency — already-merged -> no-op), TC-09 (AC-7: never-raise) and TC-13
(AC-8/INV-2: self-hosting safety — no prod restart, no direct/force push to main).
Gitea HTTP is mocked; the actor must NEVER shell out to git/docker/ssh.
"""

import httpx
import pytest

from src import merge_gate


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, "merge_verify_enabled", True)
    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-07: an OPEN PR -> the actor calls Gitea POST /pulls/{index}/merge (Do: merge).
# ---------------------------------------------------------------------------
def test_tc07_merge_actor_calls_gitea_merge(monkeypatch):
    monkeypatch.setattr(merge_gate, "pr_already_merged", lambda r, b: False)

    branch = "feature/ORCH-071-x"
    get_calls, post_calls = [], []

    def fake_get(url, params=None, headers=None, timeout=None):
        get_calls.append((url, params))
        return _Resp(200, [{"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
    assert "PR #7" in msg
    # POST hit the PR-merge API endpoint with Do=merge.
    assert len(post_calls) == 1
    url, body = post_calls[0]
    assert url.endswith("/repos/admin/orchestrator/pulls/7/merge")
    assert body == {"Do": "merge"}


# ---------------------------------------------------------------------------
# TC-08 (AC-9): already-merged PR -> no-op (no second merge, no Gitea error).
# ---------------------------------------------------------------------------
def test_tc08_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 must be made when already merged")

    monkeypatch.setattr(httpx, "get", must_not_call)
    monkeypatch.setattr(httpx, "post", must_not_call)

    ok, msg = merge_gate.merge_pr("orchestrator", "feature/ORCH-071-x")
    assert ok is True
    assert msg == "already-merged"


def test_tc08_no_open_pr_is_not_an_error(monkeypatch):
    monkeypatch.setattr(merge_gate, "pr_already_merged", lambda r, b: False)
    monkeypatch.setattr(httpx, "get", lambda *a, **k: _Resp(200, []))
    ok, msg = merge_gate.merge_pr("orchestrator", "feature/ORCH-071-x")
    assert ok is False
    assert msg == "no open PR"


# ---------------------------------------------------------------------------
# TC-09 (AC-7): a Gitea HTTP error -> (False, reason), exception not propagated.
# ---------------------------------------------------------------------------
def test_tc09_never_raise_on_http_error(monkeypatch):
    monkeypatch.setattr(merge_gate, "pr_already_merged", lambda r, b: False)

    def boom(*a, **k):
        raise httpx.ConnectError("gitea unreachable")

    monkeypatch.setattr(httpx, "get", boom)
    ok, msg = merge_gate.merge_pr("orchestrator", "feature/ORCH-071-x")
    assert ok is False
    assert "merge error" in msg


def test_tc09_merge_endpoint_non_2xx_is_false(monkeypatch):
    monkeypatch.setattr(merge_gate, "pr_already_merged", lambda r, b: False)
    monkeypatch.setattr(
        httpx, "get", lambda *a, **k: _Resp(200, [{"head": {"ref": "feature/ORCH-071-x"}, "base": {"ref": "main"}, "number": 3}])
    )
    monkeypatch.setattr(httpx, "post", lambda *a, **k: _Resp(409, text="conflict"))
    ok, msg = merge_gate.merge_pr("orchestrator", "feature/ORCH-071-x")
    assert ok is False
    assert "HTTP 409" in msg


# ---------------------------------------------------------------------------
# TC-13 (AC-8/INV-2): the merge-actor NEVER shells out (no git push/force-push,
# no docker/ssh prod restart) — the only side effect is the Gitea PR-merge API.
# ---------------------------------------------------------------------------
def test_tc13_no_shell_out_no_force_push(monkeypatch):
    monkeypatch.setattr(merge_gate, "pr_already_merged", lambda r, b: False)
    monkeypatch.setattr(
        httpx, "get", lambda *a, **k: _Resp(200, [{"head": {"ref": "feature/ORCH-071-x"}, "base": {"ref": "main"}, "number": 9}])
    )
    monkeypatch.setattr(httpx, "post", lambda *a, **k: _Resp(200))

    subprocess_calls = []
    monkeypatch.setattr(
        merge_gate.subprocess, "run",
        lambda cmd, *a, **k: subprocess_calls.append(cmd),
    )

    ok, _ = merge_gate.merge_pr("orchestrator", "feature/ORCH-071-x")
    assert ok is True
    # No subprocess (git/docker/ssh) was invoked by the merge-actor at all.
    assert subprocess_calls == []
