"""W18B base-layout floor-plan image smoke — minimal safety tests only.

3 tests:
1. dry-run safety (no API call, no PNG).
2. --generate uses the monkeypatched openai caller EXACTLY ONCE and
   only when OPENAI_API_KEY is present.
3. methodology grep — script source must not embed scenario-specific
   tokens. Per-char assembly so this assertion does not self-match.
"""
from __future__ import annotations

import json
import re
import sys
from pathlib import Path


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


def _seed_w18a_run(run_dir: Path) -> Path:
    """Create a minimal W18A2 success run dir layout that W18B loads."""
    src = run_dir / "w18a_fake"
    src.mkdir(parents=True, exist_ok=True)
    (src / "base_layout_prompt.json").write_text(json.dumps({
        "base_layout_prompt_by_fp": {
            "fp_l05_01": {
                "base_fp_t2i_prompt_text": (
                    "Schematic plan. Markers #1 #2 #3. Do not renumber, "
                    "omit, or invent."
                ),
                "included_marker_legend": [
                    {"marker_number": 1, "source_candidate_number": 1,
                     "base_layer_decision": "base_structural_unit",
                     "label": "u1", "unit_id": "U1",
                     "visual_encoding": "filled_area",
                     "must_be_legible": True},
                    {"marker_number": 2, "source_candidate_number": 2,
                     "base_layer_decision": "base_structural_unit",
                     "label": "u2", "unit_id": "U2",
                     "visual_encoding": "filled_area",
                     "must_be_legible": True},
                    {"marker_number": 3, "source_candidate_number": 3,
                     "base_layer_decision": "base_structural_unit",
                     "label": "u3", "unit_id": "U3",
                     "visual_encoding": "filled_area",
                     "must_be_legible": True},
                ],
                "excluded_transient_elements": [],
                "bg_state_overlay_payload_by_bg": {},
                "base_fp_contract_notes": "compact open zone notes.",
                "production_prompt_delta_recommendations": "review-only.",
            },
        },
    }))
    (src / "run_meta.json").write_text(json.dumps({
        "run_id": "w18a_fake",
        "stage": "w18a_floor_plan_base_layout_prompt_slice",
        "run_status": "succeeded", "exit_code": 0,
        "stage_status": "generated", "model_used": "gpt-5.5",
        "llm_api_call_count": 1, "image_api_call_count": 0,
    }))
    return src


def test_w18b_dry_run_makes_no_api_call_and_writes_no_png(tmp_path):
    """Default (no --generate) must be a pure dry-run: no PNG, no openai
    API call. image_api_call_count == 0."""
    import experiment_floor_plan_base_layout_image_smoke_slice as mod

    src = _seed_w18a_run(tmp_path)
    out_root = tmp_path / "out"
    exit_code = mod.main([
        "--derive-base-layout-image-smoke-from", str(src),
        "--target-fp-ids", "fp_l05_01",
        "--output-root", str(out_root),
    ])
    assert exit_code == 0
    runs = sorted(out_root.iterdir())
    assert len(runs) == 1
    run_dir = runs[0]
    meta = json.loads((run_dir / "run_meta.json").read_text())
    assert meta["run_status"] == "succeeded"
    assert meta["stage_status"] == "dry_run"
    assert meta["image_api_call_count"] == 0
    assert meta["image_generation_count"] == 0
    assert (
        not (run_dir / "png").exists()
        or not any((run_dir / "png").iterdir())
    )


def test_w18b_generate_uses_monkeypatched_caller_exactly_once(
    tmp_path, monkeypatch,
):
    """--generate must (a) refuse to run when OPENAI_API_KEY is absent,
    (b) when patched with a fake caller, invoke it EXACTLY ONCE for
    fp_l05_01, (c) write the PNG bytes returned by the caller to
    run-local png/."""
    import experiment_floor_plan_base_layout_image_smoke_slice as mod

    monkeypatch.delenv("OPENAI_API_KEY", raising=False)
    monkeypatch.setattr(mod, "_load_backend_env", lambda: None, raising=False)
    src = _seed_w18a_run(tmp_path)
    out_root = tmp_path / "out_nokey"
    exit_code = mod.main([
        "--derive-base-layout-image-smoke-from", str(src),
        "--target-fp-ids", "fp_l05_01",
        "--output-root", str(out_root),
        "--generate",
    ])
    assert exit_code == 1
    meta_nokey = json.loads(
        sorted(out_root.iterdir())[0].joinpath("run_meta.json").read_text()
    )
    assert meta_nokey["run_status"] == "validation_failed"
    assert (
        "openai_api_key" in str(meta_nokey.get("failed_invariants", [])).lower()
        or "OPENAI_API_KEY" in str(meta_nokey)
    )

    monkeypatch.setenv("OPENAI_API_KEY", "sk-test-fake-key")
    attempts = {"count": 0, "received_prompt": None}

    def _fake_caller(
        *, fp_id, prompt, model, size, quality, target_path, client,
    ):
        attempts["count"] += 1
        attempts["received_prompt"] = prompt
        target_path.parent.mkdir(parents=True, exist_ok=True)
        target_path.write_bytes(b"\x89PNG\r\n\x1a\nFAKEW18B")
        return {
            "status": "success",
            "png_size_bytes": target_path.stat().st_size,
            "actual_api_response_meta": {"latency_ms": 5},
            "error_meta": {},
            "cost_meta": {},
        }

    monkeypatch.setattr(mod, "_openai_caller", _fake_caller, raising=False)
    monkeypatch.setattr(
        mod, "_create_openai_client", lambda: object(), raising=False,
    )

    out_root2 = tmp_path / "out_gen"
    exit_code2 = mod.main([
        "--derive-base-layout-image-smoke-from", str(src),
        "--target-fp-ids", "fp_l05_01",
        "--output-root", str(out_root2),
        "--generate",
    ])
    assert exit_code2 == 0
    assert attempts["count"] == 1, (
        f"expected exactly 1 openai call, got {attempts['count']}"
    )
    # The caller must receive the W18A base prompt verbatim — NO
    # mapping banner, NO legend prepend.
    assert "Markers #1 #2 #3" in (attempts["received_prompt"] or "")
    assert "Marker mapping contract" not in (attempts["received_prompt"] or "")

    run_dir = sorted(out_root2.iterdir())[0]
    meta = json.loads((run_dir / "run_meta.json").read_text())
    assert meta["run_status"] == "succeeded"
    assert meta["stage_status"] == "generated"
    assert meta["image_api_call_count"] == 1
    assert meta["image_generation_count"] == 1
    png_path = run_dir / "png" / "fp_l05_01.png"
    assert png_path.exists() and png_path.stat().st_size > 0


def test_w18b_methodology_grep_no_scenario_specific_static_tokens():
    """Static script body must not embed scenario-specific tokens.
    Per-char assembly so this assertion source does not match itself."""
    script_path = (
        _SCRIPTS_DIR
        / "experiment_floor_plan_base_layout_image_smoke_slice.py"
    )
    assert script_path.exists(), f"script missing: {script_path}"
    forbidden_tokens = [
        "b" + "edroom", "ki" + "tchen", "blood" + "stain", "cur" + "tain",
        "coo" + "ktop", "tele" + "vision", "cri" + "me", "vi" + "lla",
        "roo" + "ftop", "foot" + "print", "pol" + "ice", "de" + "ck",
        "wheel" + "house", "ba" + "throom", "su" + "ri-young",
    ]
    pat = re.compile(r"(?i)\b(" + "|".join(forbidden_tokens) + r")\b")
    m = pat.search(script_path.read_text())
    assert m is None, (
        f"{script_path.name}: scenario-specific token leaked → "
        f"{m.group(0) if m else ''}"
    )
