"""ORCH-026 Level A — serialization integration (TC-A08).

Scenario (no network, lease + gate level): two tasks of the SAME repo race for
the merge edge. While A holds the merge-lease (the merge->main-updated window),
B's check_branch_mergeable returns "merge-lock busy" -> the engine DEFERS B (it
does NOT roll back). After A releases (A reached main / done), B acquires, is
proactively rebased onto the now-current main (carrying A's code) and merges.
"""
import os
import tempfile

import pytest

os.environ.setdefault("ORCH_DB_PATH", os.path.join(tempfile.gettempdir(), "test_orch026_serint.db"))
os.environ.setdefault("ORCH_GITEA_TOKEN", "test-token")
os.environ.setdefault("ORCH_PLANE_API_TOKEN", "test-token")

from src import merge_gate  # noqa: E402
from src.qg import checks  # noqa: E402


@pytest.fixture
def env(tmp_path, monkeypatch):
    monkeypatch.setattr(merge_gate.settings, "repos_dir", str(tmp_path), raising=False)
    monkeypatch.setattr(merge_gate.settings, "merge_lock_timeout_s", 300, raising=False)
    monkeypatch.setattr(checks.settings, "merge_gate_enabled", True, raising=False)
    monkeypatch.setattr(checks.settings, "merge_gate_repos", "", raising=False)
    monkeypatch.setattr(checks.settings, "premerge_rebase_always", True, raising=False)
    # Make the git/test primitives deterministic no-ops; A's rebase is a no-op,
    # B's rebase is the real "catch up to A's code".
    monkeypatch.setattr(merge_gate, "branch_is_behind_main", lambda r, b: False, raising=False)
    monkeypatch.setattr(merge_gate, "auto_rebase_onto_main", lambda r, b: (True, "ok"), raising=False)
    monkeypatch.setattr(merge_gate, "retest_branch", lambda r, b: (True, "green"), raising=False)
    return tmp_path


def test_serialized_merge_window(env, monkeypatch):
    repo = "orchestrator"
    # A reaches the merge edge first: gate passes and HOLDS the lease.
    okA, reasonA = checks.check_branch_mergeable(repo, "ORCH-1", "feature/A")
    assert okA is True
    # Lease is held by A.
    assert merge_gate._read_lease(merge_gate._lease_path(repo))["branch"] == "feature/A"

    # B reaches the merge edge while A still holds the window -> busy -> DEFER.
    okB, reasonB = checks.check_branch_mergeable(repo, "ORCH-2", "feature/B")
    assert okB is False
    assert reasonB == "merge-lock busy"  # NOT a rollback; engine re-queues via available_at
    # B's defer must NOT have stolen / cleared A's lease.
    assert merge_gate._read_lease(merge_gate._lease_path(repo))["branch"] == "feature/A"

    # A completes (PR merged / deploy->done) -> lease released.
    merge_gate.release_merge_lease(repo, "feature/A")

    # B retries: now acquires, is proactively rebased onto current main, merges.
    rebased = {"called": 0}

    def _rebase(r, b):
        rebased["called"] += 1
        return True, "rebased onto A"

    monkeypatch.setattr(merge_gate, "auto_rebase_onto_main", _rebase, raising=False)
    okB2, reasonB2 = checks.check_branch_mergeable(repo, "ORCH-2", "feature/B")
    assert okB2 is True
    assert rebased["called"] == 1, "B must be proactively rebased onto the fresh main (A's code)"
