"""W16c grid-layout model-compare slice tests — light coverage.

Verifies:
- Gemini routing is fail-closed (no fallback, no auto retry by default).
- target_fp_ids is restricted to fp_l05_01 (wave-1 lock).
- The compatibility report swaps the model invariant key to a Gemini-
  specific one and the geometry/SVG checks are reused from W16.
- production/image guard surfaces stay live.
"""
from __future__ import annotations

import sys
from pathlib import Path

import pytest


_REPO_ROOT = Path(__file__).resolve().parents[3]
_SCRIPTS_DIR = _REPO_ROOT / "backend" / "scripts"
_TESTS_SCRIPTS_DIR = Path(__file__).resolve().parent
if str(_SCRIPTS_DIR) not in sys.path:
    sys.path.insert(0, str(_SCRIPTS_DIR))
if str(_TESTS_SCRIPTS_DIR) not in sys.path:
    sys.path.insert(0, str(_TESTS_SCRIPTS_DIR))


def _w16_synthetic_layout_topo_candidate():
    """Import the W16 synthetic fixtures so W16c tests stay generic."""
    from test_experiment_floor_plan_grid_layout_slice import (
        _synthetic_candidate,
        _synthetic_llm_grid_layout,
        _synthetic_topology_brief,
    )
    return (
        _synthetic_llm_grid_layout(),
        _synthetic_topology_brief(),
        _synthetic_candidate(),
    )


def test_w16c_gemini_routing_fail_closed_and_no_retry(monkeypatch):
    """The W16c Gemini caller must:
    - refuse to run when GEMINI_API_KEY / GOOGLE_API_KEY are absent;
    - refuse any model id that is NOT exactly the expected
      `gemini/gemini-3.1-pro-preview` (so a typo like
      `gemini/gemini-3.5-flash` does NOT spend a litellm call before
      failing);
    - attempt exactly one litellm call by default (no retry)."""
    import experiment_floor_plan_grid_layout_model_compare_slice as mod

    monkeypatch.delenv("GEMINI_API_KEY", raising=False)
    monkeypatch.delenv("GOOGLE_API_KEY", raising=False)
    with pytest.raises(RuntimeError) as exc:
        mod._generate_w16c_via_gemini(
            {"foo": "bar"}, model="gemini/gemini-3.1-pro-preview",
        )
    assert "GEMINI_API_KEY" in str(exc.value) or "GOOGLE_API_KEY" in str(exc.value)

    monkeypatch.setenv("GEMINI_API_KEY", "test-fake-key")
    with pytest.raises(RuntimeError) as exc2:
        mod._generate_w16c_via_gemini({"foo": "bar"}, model="gpt-5.5")
    assert "gemini" in str(exc2.value).lower()

    attempts = {"count": 0}

    class _FakeLitellm:
        @staticmethod
        def completion(*_, **__):
            attempts["count"] += 1
            raise RuntimeError("simulated transient gemini failure")

    monkeypatch.setitem(sys.modules, "litellm", _FakeLitellm)
    with pytest.raises(RuntimeError):
        mod._generate_w16c_via_gemini(
            {"foo": "bar"}, model="gemini/gemini-3.1-pro-preview",
        )
    assert attempts["count"] == 1, (
        f"expected exactly 1 attempt by default, got {attempts['count']}"
    )


def test_w16c_exact_model_guard_blocks_other_gemini_ids(monkeypatch):
    """A `gemini/<other>` id (e.g. `gemini/gemini-3.5-flash` or
    `gemini/gemini-2.5-pro-preview`) must be rejected BEFORE the litellm
    call — exact match against `gemini/gemini-3.1-pro-preview` only.
    Verifies attempts==0 via a monkeypatched fake litellm that would
    otherwise increment on any call."""
    import experiment_floor_plan_grid_layout_model_compare_slice as mod

    monkeypatch.setenv("GEMINI_API_KEY", "test-fake-key")
    attempts = {"count": 0}

    class _FakeLitellm:
        @staticmethod
        def completion(*_, **__):
            attempts["count"] += 1
            return {"choices": [{"message": {"content": "{}"}}]}

    monkeypatch.setitem(sys.modules, "litellm", _FakeLitellm)

    rejected_ids = [
        "gemini/gemini-3.5-flash",
        "gemini/gemini-2.5-pro-preview",
        "gemini/gemini-3.1-pro",            # missing -preview suffix
        "GEMINI/gemini-3.1-pro-preview",    # different case at prefix
    ]
    for mid in rejected_ids:
        attempts["count"] = 0
        with pytest.raises(RuntimeError) as exc:
            mod._generate_w16c_via_gemini({"foo": "bar"}, model=mid)
        assert attempts["count"] == 0, (
            f"litellm.completion must NOT be called for rejected id {mid!r} "
            f"(got attempts={attempts['count']})"
        )
        # Error message must name the expected id so the caller can fix.
        assert "gemini-3.1-pro-preview" in str(exc.value)


def test_w16c_compat_report_uses_gemini_model_invariant(tmp_path):
    """The compat helper reuses W16 geometry/SVG checks but renames the
    model invariant to the Gemini-specific key. Passing GPT-5.5 to the
    Gemini-side invariant must fail; passing the Gemini id passes."""
    from experiment_floor_plan_grid_layout_model_compare_slice import (
        W16C_EXPECTED_MODEL_ID,
        W16C_MODEL_INVARIANT_KEY,
        build_w16c_compatibility_report,
    )
    layout, topo, cand = _w16_synthetic_layout_topo_candidate()

    rep = build_w16c_compatibility_report(
        grid_layout=layout, topology_brief=topo, candidate=cand,
        target_fp_ids={"FPx"},
        production_diff_empty=True, db_write_count=0,
        image_import_seen=False, image_api_call_count=0,
        svg_emitted=True, svg_paths_by_fp=None,
        model_used=W16C_EXPECTED_MODEL_ID,
        stage_status="generated", missing_inputs=[],
        prev_run_id="fakeW15e",
    )
    inv = rep["invariants"]
    # W16's original gpt-5.5 invariant key MUST NOT be present.
    assert "model_is_gpt_5_5_when_generated" not in inv
    assert W16C_MODEL_INVARIANT_KEY in inv
    assert inv[W16C_MODEL_INVARIANT_KEY]["pass"] is True

    # Wrong model id (gpt-5.5) under the Gemini invariant must fail.
    rep_bad = build_w16c_compatibility_report(
        grid_layout=layout, topology_brief=topo, candidate=cand,
        target_fp_ids={"FPx"},
        production_diff_empty=True, db_write_count=0,
        image_import_seen=False, image_api_call_count=0,
        svg_emitted=True, svg_paths_by_fp=None,
        model_used="gpt-5.5",
        stage_status="generated", missing_inputs=[],
        prev_run_id="fakeW15e",
    )
    assert rep_bad["invariants"][W16C_MODEL_INVARIANT_KEY]["pass"] is False


def test_w16c_target_fp_only_fp_l05_01_and_production_guard(tmp_path):
    """Wave-1 restriction: target_fp_ids subset of {fp_l05_01}. Also the
    combined production/db/image guard must still surface every condition."""
    from experiment_floor_plan_grid_layout_model_compare_slice import (
        W16C_EXPECTED_MODEL_ID,
        build_w16c_compatibility_report,
    )
    layout, topo, cand = _w16_synthetic_layout_topo_candidate()

    rep = build_w16c_compatibility_report(
        grid_layout=layout, topology_brief=topo, candidate=cand,
        target_fp_ids={"FPx", "FPy"},
        production_diff_empty=True, db_write_count=0,
        image_import_seen=False, image_api_call_count=0,
        svg_emitted=True, svg_paths_by_fp=None,
        model_used=W16C_EXPECTED_MODEL_ID,
        stage_status="generated", missing_inputs=[],
        prev_run_id="fakeW15e",
    )
    # The wrapper does not loosen W16's allowed-fp restriction.
    assert rep["invariants"]["target_fp_only_fp_l05_01"]["pass"] is False

    # Production/image guard fails if image API call seen.
    rep_img = build_w16c_compatibility_report(
        grid_layout=layout, topology_brief=topo, candidate=cand,
        target_fp_ids={"FPx"},
        production_diff_empty=True, db_write_count=0,
        image_import_seen=False, image_api_call_count=1,
        svg_emitted=True, svg_paths_by_fp=None,
        model_used=W16C_EXPECTED_MODEL_ID,
        stage_status="generated", missing_inputs=[],
        prev_run_id="fakeW15e",
    )
    assert rep_img["invariants"][
        "production_diff_zero_db_write_zero_image_api_call_zero"
    ]["pass"] is False


def test_w16c_svg_well_formed_invariant_blocks_invalid_xml(tmp_path):
    """Reusing W16's SVG renderer means the same XML well-formedness
    guard applies. Intentional corruption must fail the invariant."""
    from experiment_floor_plan_grid_layout_model_compare_slice import (
        W16C_EXPECTED_MODEL_ID,
        build_w16c_compatibility_report,
    )
    from experiment_floor_plan_grid_layout_slice import _render_w16_svg

    layout, topo, cand = _w16_synthetic_layout_topo_candidate()
    out_path = _render_w16_svg(
        fp_id="FPx",
        fp_layout=layout["grid_layout_by_fp"]["FPx"],
        run_dir=tmp_path,
    )
    rep_ok = build_w16c_compatibility_report(
        grid_layout=layout, topology_brief=topo, candidate=cand,
        target_fp_ids={"FPx"},
        production_diff_empty=True, db_write_count=0,
        image_import_seen=False, image_api_call_count=0,
        svg_emitted=True, svg_paths_by_fp={"FPx": out_path},
        model_used=W16C_EXPECTED_MODEL_ID,
        stage_status="generated", missing_inputs=[],
        prev_run_id="fakeW15e",
    )
    assert rep_ok["invariants"]["svg_is_well_formed_xml"]["pass"] is True

    out_path.write_text("<svg><text>>1<</text></svg>")
    rep_bad = build_w16c_compatibility_report(
        grid_layout=layout, topology_brief=topo, candidate=cand,
        target_fp_ids={"FPx"},
        production_diff_empty=True, db_write_count=0,
        image_import_seen=False, image_api_call_count=0,
        svg_emitted=True, svg_paths_by_fp={"FPx": out_path},
        model_used=W16C_EXPECTED_MODEL_ID,
        stage_status="generated", missing_inputs=[],
        prev_run_id="fakeW15e",
    )
    assert rep_bad["invariants"]["svg_is_well_formed_xml"]["pass"] is False
