"""W18H remaining L05 background smokes — minimal tests.

2 tests:
1. happy path: fake generate over 4 allowed BGs writes 4 PNGs and
   counters image_api_call_count=4, image_generation_count=4; dry-run
   skips the caller entirely.
2. L05B04 rejected from W18H target set + methodology grep guard.
"""
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 _make_fake_w18f_run(tmp_path: Path):
    """Build a minimal W18F-shaped run dir on tmp_path covering 4 BGs."""
    run_dir = tmp_path / "w18f_fake_run"
    run_dir.mkdir()
    png_dir = tmp_path / "fake_png"
    png_dir.mkdir(exist_ok=True)
    ref_png = png_dir / "fp.png"
    ref_png.write_bytes(b"\x89PNG\r\n\x1a\nfake-base-fp")
    base_fp_path_str = str(ref_png)

    def _bg(bg_id, transient_nums, clean):
        return {
            "bg_id": bg_id, "fp_id": "fp_l05_01",
            "base_fp_ref_path": base_fp_path_str,
            "assembled_background_prompt_preview": (
                f"BG {bg_id} prose + assembled appendix."
            ),
            "transient_overlay_marker_numbers": transient_nums,
            "clean_background_expected": clean,
            "consumer_decision_preview": (
                "READY_FOR_BACKGROUND_PROMPT_DRY_RUN"
            ),
        }

    assembly = {
        "assembly_preview_by_bg": {
            "L05B01": _bg("L05B01", [21], False),
            "L05B02": _bg("L05B02", [], True),
            "L05B03": _bg("L05B03", [20, 22], False),
            "L05B05": _bg("L05B05", [], True),
            "L05B04": _bg("L05B04", [18, 19, 23], False),
        },
        "fp_id": "fp_l05_01",
    }
    (run_dir / "background_prompt_region_ref_assembly_preview.json").write_text(
        json.dumps(assembly, ensure_ascii=False)
    )
    (run_dir / "run_meta.json").write_text(
        json.dumps({"run_id": "w18f_fake", "stage": "w18f"})
    )
    return run_dir, ref_png


def test_w18h_dry_run_skips_and_fake_generate_writes_four_pngs(tmp_path):
    """Dry-run: no openai call, no PNGs. Fake generate: 4 BGs, 4 PNGs,
    counters match, all invariants PASS."""
    import experiment_background_image_smoke_remaining_l05_slice as mod

    w18f_dir, _ref = _make_fake_w18f_run(tmp_path)
    out_root = tmp_path / "w18h_out"

    def _explode(**_kw):
        raise AssertionError("caller invoked in dry-run mode")

    orig = mod._openai_edit_caller
    mod._openai_edit_caller = _explode
    try:
        rc_dry = mod.main([
            "--derive-background-smoke-from", str(w18f_dir),
            "--target-bg-ids", "L05B01,L05B02,L05B03,L05B05",
            "--output-root", str(out_root),
        ])
    finally:
        mod._openai_edit_caller = orig
    assert rc_dry == 0
    dry_dirs = sorted(out_root.iterdir())
    assert len(dry_dirs) == 1
    dry_meta = json.loads((dry_dirs[0] / "run_meta.json").read_text())
    assert dry_meta["mode"] == "dry_run"
    assert dry_meta["image_api_call_count"] == 0
    assert dry_meta["image_generation_count"] == 0
    for bg in ("L05B01", "L05B02", "L05B03", "L05B05"):
        assert not (dry_dirs[0] / "png" / f"{bg}.png").exists()
    dry_report = json.loads(
        (dry_dirs[0] / "w18h_compatibility_report.json").read_text()
    )
    assert dry_report["all_pass"] is True, dry_report

    # Fake generate: monkeypatch to write small PNGs per BG.
    def _fake_edit(*, bg_id, prompt, model, size, base_fp_png_path,
                   target_path, client, **_kw):
        target_path.parent.mkdir(parents=True, exist_ok=True)
        target_path.write_bytes(
            (b"\x89PNG\r\n\x1a\nfake-" + bg_id.encode()).ljust(64, b"0")
        )
        return {
            "status": "success",
            "png_size_bytes": target_path.stat().st_size,
            "actual_api_response_meta": {"latency_ms": 1},
            "error_meta": {},
            "cost_meta": {"provider_usage": {"fake": True}},
        }

    class _FakeClient:
        pass

    def _fake_client():
        return _FakeClient()

    mod._openai_edit_caller = _fake_edit
    mod._create_openai_client = _fake_client  # type: ignore[attr-defined]
    import os
    prev_key = os.environ.get("OPENAI_API_KEY")
    os.environ["OPENAI_API_KEY"] = "sk-fake-for-test"
    try:
        rc_gen = mod.main([
            "--derive-background-smoke-from", str(w18f_dir),
            "--target-bg-ids", "L05B01,L05B02,L05B03,L05B05",
            "--generate", "--model", "gpt-image-2",
            "--output-root", str(out_root),
        ])
    finally:
        mod._openai_edit_caller = orig
        if prev_key is None:
            os.environ.pop("OPENAI_API_KEY", None)
        else:
            os.environ["OPENAI_API_KEY"] = prev_key
    assert rc_gen == 0

    gen_dirs = sorted(out_root.iterdir())
    assert len(gen_dirs) == 2
    gen_dir = [d for d in gen_dirs if d != dry_dirs[0]][0]
    gen_meta = json.loads((gen_dir / "run_meta.json").read_text())
    assert gen_meta["mode"] == "generated"
    assert gen_meta["image_api_call_count"] == 4
    assert gen_meta["image_generation_count"] == 4
    for bg in ("L05B01", "L05B02", "L05B03", "L05B05"):
        p = gen_dir / "png" / f"{bg}.png"
        assert p.exists() and p.stat().st_size > 0, bg
    gen_report = json.loads(
        (gen_dir / "w18h_compatibility_report.json").read_text()
    )
    assert gen_report["all_pass"] is True, gen_report


def test_w18h_l05b04_rejected_and_methodology_grep(tmp_path):
    """L05B04 included in target set → invariant 2 / failure surface.
    Plus methodology grep against scenario-specific tokens."""
    import experiment_background_image_smoke_remaining_l05_slice as mod

    w18f_dir, _ref = _make_fake_w18f_run(tmp_path)
    out_root = tmp_path / "w18h_out_bad"
    rc = mod.main([
        "--derive-background-smoke-from", str(w18f_dir),
        "--target-bg-ids", "L05B01,L05B04",
        "--output-root", str(out_root),
    ])
    assert rc == 1
    bad_dir = sorted(out_root.iterdir())[0]
    bad_meta = json.loads((bad_dir / "run_meta.json").read_text())
    # Either invalid_target_bg_ids OR l05b04_not_allowed_in_w18h must
    # fire before any image attempt.
    assert (
        "invalid_target_bg_ids" in bad_meta["failed_invariants"]
        or "l05b04_not_allowed_in_w18h" in bad_meta["failed_invariants"]
    ), bad_meta["failed_invariants"]

    script_path = (
        _SCRIPTS_DIR
        / "experiment_background_image_smoke_remaining_l05_slice.py"
    )
    assert script_path.exists()
    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 ''}"
    )
