"""ORCH-110 TC-08: re-test budget validation + cross-invariants (D5).

Covers FR-3 / AC-5 / NFR-6:
  * ``_resolve_retest_timeout`` validates the config (malformed / non-positive ->
    safe default 900 + WARNING; never reaches subprocess);
  * the budget was bumped 600 -> 900;
  * the cross-invariant ``reaper_max_running_s > Σ(deploy-staging gate-work) + grace``
    (ORCH-065/109) still holds with the new 900s re-test budget — WITHOUT raising
    ``reaper_max_running_s``.
"""
import os
import tempfile

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

import pytest  # noqa: E402

from src import merge_gate  # noqa: E402
from src.config import Settings, settings  # noqa: E402


# ---------------------------------------------------------------------------
# _resolve_retest_timeout — validation (never-break).
# ---------------------------------------------------------------------------
def test_tc08_resolve_uses_positive_config(monkeypatch):
    monkeypatch.setattr(merge_gate.settings, "merge_retest_timeout_s", 1234, raising=False)
    assert merge_gate._resolve_retest_timeout() == 1234


@pytest.mark.parametrize("bad", [0, -5, "abc", None, 3.0])
def test_tc08_resolve_bad_config_falls_back_to_default(monkeypatch, bad):
    monkeypatch.setattr(merge_gate.settings, "merge_retest_timeout_s", bad, raising=False)
    # 3.0 is a valid positive int(3) -> stays 3; everything else -> 900 default.
    out = merge_gate._resolve_retest_timeout()
    if bad == 3.0:
        assert out == 3
    else:
        assert out == 900


def test_tc08_default_budget_bumped_to_900():
    """D5: the shipped default budget is 900 (raised from 600)."""
    assert Settings().merge_retest_timeout_s == 900


# ---------------------------------------------------------------------------
# Cross-invariant: reaper backstop covers the worst-case deploy-staging gate-work.
# ---------------------------------------------------------------------------
def test_tc08_reaper_covers_deploy_staging_worstcase():
    """ORCH-065/109 invariant with the new 900s re-test budget (ADR D5 table)."""
    s = Settings()
    # Worst-case sum of work charged to a deploy-staging-deployer job (ADR D5).
    security = 120
    rebase = 120
    image = 600
    worst = (
        s.agent_timeout_seconds          # deployer agent (1800)
        + security
        + rebase
        + s.merge_retest_timeout_s       # re-test (900, new)
        + s.coverage_run_timeout_s       # coverage (900)
        + image
        + s.agent_kill_grace_seconds     # grace (20)
    )
    assert worst <= 4460  # matches the ADR D5 table
    assert s.reaper_max_running_s > worst, (
        f"reaper_max_running_s={s.reaper_max_running_s} must exceed "
        f"deploy-staging worst-case {worst}"
    )


def test_tc08_reaper_still_covers_max_agent_timeout():
    """ORCH-065/109: reaper_max_running_s > max(agent timeout) + grace (unchanged)."""
    s = Settings()
    assert s.reaper_max_running_s > s.agent_timeout_developer_s + s.agent_kill_grace_seconds


def test_tc08_reaper_max_running_s_unchanged():
    """D5 must NOT change reaper_max_running_s (stays 5400 from ORCH-109)."""
    assert settings.reaper_max_running_s == 5400
