"""ORCH-071 — Phase C finalizer x merge-verify under-gate (integration).

Covers TC-05 (FR-3/G2/AC-1: deploy SUCCESS but PR open -> NOT done + alert),
TC-06 (AC-4: deploy SUCCESS + merge confirmed -> done) and TC-14 (AC-11: Phase B
runs only on confirm_deploy; merge/verify never introduce an auto-deploy).

Mirrors tests/test_deploy_terminal_sync.py: the finalizer drives advance_stage,
the deploy gate is forced green, and the merge-actor/verifier are mocked so the
test stays deterministic (no real Gitea/git).
"""

import os
import tempfile

import pytest

_test_db = os.path.join(tempfile.gettempdir(), "test_orch_merge_verify.db")
os.environ["ORCH_DB_PATH"] = _test_db
os.environ["ORCH_REPOS_DIR"] = tempfile.gettempdir()
os.environ.setdefault("ORCH_GITEA_TOKEN", "test-token")
os.environ.setdefault("ORCH_PLANE_API_TOKEN", "test-token")

from unittest.mock import MagicMock  # noqa: E402

import src.db as _db  # noqa: E402
from src.db import init_db, get_db  # noqa: E402
from src import stage_engine  # noqa: E402
from src import self_deploy  # noqa: E402


@pytest.fixture(autouse=True)
def fresh_db(monkeypatch, tmp_path):
    monkeypatch.setattr(_db.settings, "db_path", _test_db)
    if os.path.exists(_test_db):
        os.unlink(_test_db)
    init_db()
    monkeypatch.setattr(self_deploy.settings, "repos_dir", str(tmp_path))
    monkeypatch.setattr(self_deploy.settings, "host_repos_dir", str(tmp_path))
    monkeypatch.setattr(stage_engine.self_deploy, "write_deploy_log", MagicMock(return_value=True))
    # The under-gate is disabled by conftest default; these tests target it.
    monkeypatch.setattr(stage_engine.merge_gate.settings, "merge_verify_enabled", True)
    monkeypatch.setattr(stage_engine.merge_gate.settings, "merge_verify_repos", "")
    # The merged_to_main stamp is an observability side effect (no log file here).
    monkeypatch.setattr(
        stage_engine.self_deploy, "record_merged_to_main", MagicMock(return_value=True)
    )
    # ORCH-021 post-deploy monitor is orthogonal; keep it off for these tests.
    monkeypatch.setattr(stage_engine.post_deploy.settings, "post_deploy_monitor_enabled", False)
    yield


@pytest.fixture(autouse=True)
def silence_side_effects(monkeypatch):
    for name in (
        "notify_stage_change", "notify_qg_failure", "notify_approve_requested",
        "send_telegram", "plane_notify_stage", "plane_notify_qg", "plane_add_comment",
        "set_issue_in_review", "set_issue_needs_input", "set_issue_in_progress",
        "set_issue_blocked", "set_issue_done", "set_issue_analysis",
        "set_issue_awaiting_deploy", "set_issue_deploying", "set_issue_monitoring",
    ):
        monkeypatch.setattr(stage_engine, name, MagicMock())
    monkeypatch.setattr(stage_engine.merge_gate, "release_merge_lease", MagicMock())


def _pass(*a, **k):
    return (True, "ok")


def _make_task(stage, repo="orchestrator", branch="feature/ORCH-071-x", wi="ORCH-071"):
    conn = get_db()
    cur = conn.execute(
        "INSERT INTO tasks (plane_id, work_item_id, repo, branch, stage) VALUES (?, ?, ?, ?, ?)",
        (f"plane-{wi}", wi, repo, branch, stage),
    )
    task_id = cur.lastrowid
    conn.commit()
    conn.close()
    return task_id


def _stage(task_id):
    conn = get_db()
    row = conn.execute("SELECT stage FROM tasks WHERE id=?", (task_id,)).fetchone()
    conn.close()
    return row[0]


def _force_deploy_gate_green(monkeypatch):
    monkeypatch.setattr(
        stage_engine, "QG_CHECKS",
        {**stage_engine.QG_CHECKS, "check_deploy_status": _pass},
    )


# ---------------------------------------------------------------------------
# TC-05 (AC-1): deploy_status=SUCCESS but PR open -> task is HELD (not done) + alert.
# ---------------------------------------------------------------------------
def test_tc05_success_but_not_merged_holds_and_alerts(monkeypatch):
    self_deploy.write_marker("orchestrator", "ORCH-071", self_deploy.RESULT, "0")
    _force_deploy_gate_green(monkeypatch)
    # The merge-actor finds no merge and the verifier confirms NOT merged.
    monkeypatch.setattr(stage_engine.merge_gate, "merge_pr", MagicMock(return_value=(False, "no open PR")))
    monkeypatch.setattr(stage_engine.merge_gate, "verify_merged_to_main", MagicMock(return_value=False))

    task_id = _make_task("deploy")
    stage_engine.run_deploy_finalizer(
        {"task_id": task_id, "repo": "orchestrator", "id": 1, "agent": "deploy-finalizer"}
    )

    # AC-1 PASS: the task did NOT reach done and was Blocked for manual handling.
    assert _stage(task_id) == "deploy"
    assert stage_engine.set_issue_blocked.called
    assert not stage_engine.set_issue_done.called
    assert not stage_engine.set_issue_monitoring.called
    # An alert was sent ("deploy succeeded but not merged").
    assert stage_engine.send_telegram.called


# ---------------------------------------------------------------------------
# TC-06 (AC-4): deploy_status=SUCCESS + merge confirmed -> done (happy-path).
# ---------------------------------------------------------------------------
def test_tc06_success_and_merged_reaches_done(monkeypatch):
    self_deploy.write_marker("orchestrator", "ORCH-071", self_deploy.RESULT, "0")
    _force_deploy_gate_green(monkeypatch)
    merge_pr = MagicMock(return_value=(True, "merged PR #1"))
    verify = MagicMock(return_value=True)
    monkeypatch.setattr(stage_engine.merge_gate, "merge_pr", merge_pr)
    monkeypatch.setattr(stage_engine.merge_gate, "verify_merged_to_main", verify)

    task_id = _make_task("deploy")
    stage_engine.run_deploy_finalizer(
        {"task_id": task_id, "repo": "orchestrator", "id": 1, "agent": "deploy-finalizer"}
    )

    assert _stage(task_id) == "done"
    # The deterministic merge-actor + verifier both ran on the deploy->done edge.
    assert merge_pr.called
    assert verify.called
    # Self-hosting: terminal status -> Monitoring (post_deploy off here -> Done set).
    assert not stage_engine.set_issue_blocked.called


# ---------------------------------------------------------------------------
# TC-14 (AC-11): a plain Approved on `deploy` (confirm_deploy=False) is a no-op —
# Phase B (prod deploy) requires "Confirm Deploy", and merge/verify do NOT run
# (the under-gate never introduces an auto-deploy).
# ---------------------------------------------------------------------------
def test_tc14_plain_approved_on_deploy_is_noop_no_merge(monkeypatch):
    monkeypatch.setattr(stage_engine.self_deploy.settings, "self_deploy_enabled", True)
    monkeypatch.setattr(stage_engine.self_deploy.settings, "self_deploy_repos", "")
    monkeypatch.setattr(stage_engine.settings, "deploy_require_manual_approve", True)

    merge_pr = MagicMock()
    verify = MagicMock()
    initiate = MagicMock(return_value=(True, "ok"))
    monkeypatch.setattr(stage_engine.merge_gate, "merge_pr", merge_pr)
    monkeypatch.setattr(stage_engine.merge_gate, "verify_merged_to_main", verify)
    monkeypatch.setattr(stage_engine.self_deploy, "initiate_deploy", initiate)

    task_id = _make_task("deploy")
    # finished_agent=None + confirm_deploy=False == a plain Approved on `deploy`.
    result = stage_engine.advance_stage(
        task_id, "deploy", "orchestrator", "ORCH-071", "feature/ORCH-071-x",
        finished_agent=None, confirm_deploy=False,
    )

    assert result.note == "approved-on-deploy-noop"
    assert _stage(task_id) == "deploy"
    # No prod deploy initiated and the merge-verify under-gate never fired.
    assert not initiate.called
    assert not merge_pr.called
    assert not verify.called


def test_tc14_confirm_deploy_initiates_phase_b(monkeypatch):
    monkeypatch.setattr(stage_engine.self_deploy.settings, "self_deploy_enabled", True)
    monkeypatch.setattr(stage_engine.self_deploy.settings, "self_deploy_repos", "")
    monkeypatch.setattr(stage_engine.settings, "deploy_require_manual_approve", True)
    initiate = MagicMock(return_value=(True, "ok"))
    monkeypatch.setattr(stage_engine.self_deploy, "initiate_deploy", initiate)

    task_id = _make_task("deploy")
    stage_engine.advance_stage(
        task_id, "deploy", "orchestrator", "ORCH-071", "feature/ORCH-071-x",
        finished_agent=None, confirm_deploy=True,
    )
    # Only the dedicated "Confirm Deploy" signal initiates the prod deploy.
    assert initiate.called
