"""W14b structural tests — generic synthetic fixtures only.

Network 0 — the OpenAI image wrapper is monkeypatched. No real API call is
made by these tests. No scenario-specific tokens in methodology.
"""
from __future__ import annotations

import json
import re
import sys
from pathlib import Path

import pytest


_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 _synthetic_w14_payloads() -> dict:
    """Three abstract candidate fp payloads — identical shape to W14 output."""
    return {
        "FPa": {
            "fp_id": "FPa", "group_id_pointer": "Ga",
            "candidate_diagram_t2i_prompt": "schematic plan alpha.",
            "candidate_key_elements": [],
            "candidate_numbered_elements": [{"number": 1, "label": "x"}],
            "expected_image_model": "gpt-image-2",
            "api_method_preview": "images.generate",
            "api_call_shape": {"client_method": "images.generate",
                               "model": "gpt-image-2", "size": "1024x1024",
                               "quality": "high", "n": 1},
            "expected_output_kind": "candidate_floor_plan_png",
            "expected_output_png_path": "/fake/FPa.png",
        },
        "FPb": {
            "fp_id": "FPb", "group_id_pointer": "Gb",
            "candidate_diagram_t2i_prompt": "schematic plan beta.",
            "candidate_key_elements": [],
            "candidate_numbered_elements": [{"number": 1, "label": "y"}],
            "expected_image_model": "gpt-image-2",
            "api_method_preview": "images.generate",
            "api_call_shape": {"client_method": "images.generate",
                               "model": "gpt-image-2", "size": "1024x1024",
                               "quality": "high", "n": 1},
            "expected_output_kind": "candidate_floor_plan_png",
            "expected_output_png_path": "/fake/FPb.png",
        },
        "FPc": {
            "fp_id": "FPc", "group_id_pointer": "Gc",
            "candidate_diagram_t2i_prompt": "schematic plan gamma.",
            "candidate_key_elements": [],
            "candidate_numbered_elements": [{"number": 1, "label": "z"}],
            "expected_image_model": "gpt-image-2",
            "api_method_preview": "images.generate",
            "api_call_shape": {"client_method": "images.generate",
                               "model": "gpt-image-2", "size": "1024x1024",
                               "quality": "high", "n": 1},
            "expected_output_kind": "candidate_floor_plan_png",
            "expected_output_png_path": "/fake/FPc.png",
        },
    }


def _synthetic_w14_payloads_with_assembled():
    """W14C payload shape: original `candidate_diagram_t2i_prompt` preserved
    plus the new `numbered_marker_contract_appendix` and
    `assembled_candidate_diagram_prompt` fields."""
    p = _synthetic_w14_payloads()
    for fp_id in p:
        base = p[fp_id]["candidate_diagram_t2i_prompt"]
        appendix = f"contract appendix for {fp_id}: #1 #7 #28 listed."
        p[fp_id]["numbered_marker_contract_appendix"] = appendix
        p[fp_id]["assembled_candidate_diagram_prompt"] = base + "\n\n" + appendix
    return p


def test_w14b_uses_assembled_prompt_when_present(tmp_path):
    """W14b prompt selection: assembled_candidate_diagram_prompt wins when
    present, falling back to candidate_diagram_t2i_prompt otherwise. The
    fake openai caller captures the prompt actually passed in."""
    from experiment_actual_floor_plan_generation_slice import _build_results

    captured: dict = {}

    def capturing_caller(*, fp_id, prompt, model, size, quality, target_path,
                         client):
        captured[fp_id] = prompt
        target_path.parent.mkdir(parents=True, exist_ok=True)
        target_path.write_bytes(b"FAKEPNG_" + fp_id.encode())
        return {
            "status": "success",
            "png_size_bytes": target_path.stat().st_size,
            "actual_api_response_meta": {"latency_ms": 1},
            "error_meta": {}, "cost_meta": {},
        }

    # FPa carries assembled+appendix; FPb falls back (no assembled field).
    payloads = _synthetic_w14_payloads_with_assembled()
    payloads["FPb"].pop("assembled_candidate_diagram_prompt", None)
    payloads["FPb"].pop("numbered_marker_contract_appendix", None)

    _, _ = _build_results(
        payloads=payloads, effective_targets={"FPa", "FPb"},
        mode="generate", run_dir=tmp_path,
        openai_caller=capturing_caller,
    )
    assert "contract appendix for FPa" in captured["FPa"]
    # The original base prompt must still be present in the prompt sent.
    assert "schematic plan alpha." in captured["FPa"]
    # Fallback bg uses the bare candidate prompt (no contract appendix).
    assert "contract appendix" not in captured["FPb"]
    assert "schematic plan beta." in captured["FPb"]


def test_w14b_target_subset_validation():
    """`--targets all` → full set; csv → subset; unknown id → invalid_targets
    surfaced; empty set → validation_failed signal."""
    from experiment_actual_floor_plan_generation_slice import _resolve_targets

    payload_ids = {"FPa", "FPb", "FPc"}

    resolved, invalid = _resolve_targets("all", payload_ids)
    assert resolved == payload_ids
    assert invalid == []

    resolved2, invalid2 = _resolve_targets("FPa,FPc", payload_ids)
    assert resolved2 == {"FPa", "FPc"}
    assert invalid2 == []

    resolved3, invalid3 = _resolve_targets("FPa,doesNotExist", payload_ids)
    assert resolved3 == {"FPa"}
    assert invalid3 == ["doesNotExist"]

    # Whitespace tolerance.
    resolved4, invalid4 = _resolve_targets("  FPa , FPb  ", payload_ids)
    assert resolved4 == {"FPa", "FPb"}
    assert invalid4 == []

    # Empty string → empty set + no invalid (caller decides whether to fail).
    resolved5, invalid5 = _resolve_targets("", payload_ids)
    assert resolved5 == set()
    assert invalid5 == []


def test_w14b_dry_run_mode_produces_no_api_calls(tmp_path):
    """dry-run mode: all targeted results status==dry_run_skipped,
    api_call_attempt_count==0, image_generation_count==0, png dir has no
    file. Excluded targets carry status=target_excluded."""
    from experiment_actual_floor_plan_generation_slice import (
        _build_results, _build_w14b_compatibility_report,
    )
    payloads = _synthetic_w14_payloads()
    effective_targets = {"FPa", "FPb"}  # exclude FPc
    results, counters = _build_results(
        payloads=payloads, effective_targets=effective_targets,
        mode="dry_run", run_dir=tmp_path,
        openai_caller=None,  # unused in dry_run
    )
    assert counters["api_call_attempt_count"] == 0
    assert counters["image_generation_count"] == 0
    assert results["FPa"]["status"] == "dry_run_skipped"
    assert results["FPb"]["status"] == "dry_run_skipped"
    assert results["FPc"]["status"] == "target_excluded"
    assert results["FPa"]["png_path"] == ""
    assert results["FPa"]["png_size_bytes"] == 0
    assert not (tmp_path / "png" / "FPa.png").exists()

    rep = _build_w14b_compatibility_report(
        payloads=payloads, effective_targets=effective_targets,
        invalid_targets=[], results=results, mode="dry_run",
        api_call_attempt_count=counters["api_call_attempt_count"],
        image_generation_count=counters["image_generation_count"],
        production_diff_empty=True, db_write_count=0,
        image_import_seen=False, image_asset_write_count=0,
        missing_inputs=[], prev_run_id="fakeW14",
    )
    inv = rep["invariants"]
    assert inv["w14_inputs_present"]["pass"] is True
    assert inv["targets_within_payload"]["pass"] is True
    assert inv["all_targeted_results_present_and_mode_consistent"]["pass"] is True
    assert inv["model_used_was_gpt_image_2"]["pass"] is True
    assert inv["production_diff_zero_db_write_zero_no_imageasset_write"]["pass"] is True

    # Inject an invalid CLI target → targets_within_payload fails.
    rep_bad = _build_w14b_compatibility_report(
        payloads=payloads, effective_targets={"FPa"},
        invalid_targets=["doesNotExist"], results=results, mode="dry_run",
        api_call_attempt_count=0, image_generation_count=0,
        production_diff_empty=True, db_write_count=0,
        image_import_seen=False, image_asset_write_count=0,
        missing_inputs=[], prev_run_id="fakeW14",
    )
    assert rep_bad["invariants"]["targets_within_payload"]["pass"] is False


def test_w14b_generate_mode_with_fake_openai_call(tmp_path):
    """generate mode: monkeypatched OpenAI wrapper writes a stub PNG for
    success and reports api_call_failed for the failing target. Partial
    failure → run_status='partial_failed', exit_code=1. ImageAsset write
    must remain 0 throughout."""
    from experiment_actual_floor_plan_generation_slice import (
        _build_results, _build_w14b_compatibility_report,
    )
    payloads = _synthetic_w14_payloads()
    effective_targets = {"FPa", "FPb", "FPc"}

    def fake_openai_caller(*, fp_id, prompt, model, size, quality, target_path,
                           client):
        if fp_id == "FPc":
            return {
                "status": "api_call_failed",
                "png_size_bytes": 0,
                "actual_api_response_meta": {"latency_ms": 12},
                "error_meta": {"status_code": 429,
                               "message": "rate limited (synthetic)"},
                "cost_meta": {},
            }
        target_path.parent.mkdir(parents=True, exist_ok=True)
        target_path.write_bytes(b"FAKEPNGBYTES_" + fp_id.encode())
        return {
            "status": "success",
            "png_size_bytes": target_path.stat().st_size,
            "actual_api_response_meta": {"latency_ms": 8,
                                         "request_id": f"fake-{fp_id}"},
            "error_meta": {},
            "cost_meta": {"provider_usage": {"input_tokens": 1}},
        }

    results, counters = _build_results(
        payloads=payloads, effective_targets=effective_targets,
        mode="generate", run_dir=tmp_path,
        openai_caller=fake_openai_caller,
    )
    assert counters["api_call_attempt_count"] == 3
    assert counters["image_generation_count"] == 2  # FPa + FPb
    assert results["FPa"]["status"] == "success"
    assert results["FPb"]["status"] == "success"
    assert results["FPc"]["status"] == "api_call_failed"
    assert results["FPc"]["error_meta"]["status_code"] == 429
    # Success PNGs are on disk.
    assert (tmp_path / "png" / "FPa.png").exists()
    assert (tmp_path / "png" / "FPb.png").exists()
    # Failed target has no PNG file.
    assert not (tmp_path / "png" / "FPc.png").exists()

    rep = _build_w14b_compatibility_report(
        payloads=payloads, effective_targets=effective_targets,
        invalid_targets=[], results=results, mode="generate",
        api_call_attempt_count=counters["api_call_attempt_count"],
        image_generation_count=counters["image_generation_count"],
        production_diff_empty=True, db_write_count=0,
        image_import_seen=False, image_asset_write_count=0,
        missing_inputs=[], prev_run_id="fakeW14",
    )
    assert rep["invariants"]["all_targeted_results_present_and_mode_consistent"]["pass"] is True
    # success results carry gpt-image-2 model_used; failed/excluded N/A.
    assert rep["invariants"]["model_used_was_gpt_image_2"]["pass"] is True
    assert rep["invariants"]["production_diff_zero_db_write_zero_no_imageasset_write"]["pass"] is True

    # Mutate one success result to a wrong model → fails.
    bad_results = json.loads(json.dumps(results))
    bad_results["FPa"]["model_used"] = "some-other-model"
    rep_bad = _build_w14b_compatibility_report(
        payloads=payloads, effective_targets=effective_targets,
        invalid_targets=[], results=bad_results, mode="generate",
        api_call_attempt_count=3, image_generation_count=2,
        production_diff_empty=True, db_write_count=0,
        image_import_seen=False, image_asset_write_count=0,
        missing_inputs=[], prev_run_id="fakeW14",
    )
    assert rep_bad["invariants"]["model_used_was_gpt_image_2"]["pass"] is False

    # ImageAsset write > 0 → production guard fails.
    rep_asset_write = _build_w14b_compatibility_report(
        payloads=payloads, effective_targets=effective_targets,
        invalid_targets=[], results=results, mode="generate",
        api_call_attempt_count=3, image_generation_count=2,
        production_diff_empty=True, db_write_count=0,
        image_import_seen=False, image_asset_write_count=1,
        missing_inputs=[], prev_run_id="fakeW14",
    )
    assert rep_asset_write["invariants"]["production_diff_zero_db_write_zero_no_imageasset_write"]["pass"] is False


def test_w14b_html_img_embed_success_only_and_methodology_grep(tmp_path):
    """HTML first screen: <img src="png/<fp_id>.png"> only for status==success.
    dry_run / api_call_failed / target_excluded show placeholder text, no img
    tag. Raw JSON inside collapsed details. Source script methodology grep
    (per-char split tokens) finds no scenario-specific literal."""
    from experiment_actual_floor_plan_generation_slice import (
        _build_results, _build_w14b_compatibility_report, _render_w14b_html,
    )
    payloads = _synthetic_w14_payloads()
    effective_targets = {"FPa", "FPb"}  # exclude FPc

    def fake_caller(*, fp_id, prompt, model, size, quality, target_path, client):
        if fp_id == "FPb":
            return {
                "status": "api_call_failed", "png_size_bytes": 0,
                "actual_api_response_meta": {"latency_ms": 4},
                "error_meta": {"status_code": 500, "message": "server (synthetic)"},
                "cost_meta": {},
            }
        target_path.parent.mkdir(parents=True, exist_ok=True)
        target_path.write_bytes(b"FAKEPNG_" + fp_id.encode())
        return {
            "status": "success", "png_size_bytes": target_path.stat().st_size,
            "actual_api_response_meta": {"latency_ms": 5,
                                         "request_id": "fake-req"},
            "error_meta": {}, "cost_meta": {},
        }

    results, counters = _build_results(
        payloads=payloads, effective_targets=effective_targets,
        mode="generate", run_dir=tmp_path,
        openai_caller=fake_caller,
    )
    rep = _build_w14b_compatibility_report(
        payloads=payloads, effective_targets=effective_targets,
        invalid_targets=[], results=results, mode="generate",
        api_call_attempt_count=counters["api_call_attempt_count"],
        image_generation_count=counters["image_generation_count"],
        production_diff_empty=True, db_write_count=0,
        image_import_seen=False, image_asset_write_count=0,
        missing_inputs=[], prev_run_id="fakeW14",
    )
    run_meta = {
        "run_id": "RID1", "stage": "w14b_actual_floor_plan_generation_slice",
        "run_status": "partial_failed", "exit_code": 1,
        "derived_from": "fakeW14", "mode": "generate",
        "api_call_attempt_count": counters["api_call_attempt_count"],
        "image_generation_count": counters["image_generation_count"],
        "image_generation_backend": "gpt-image-2",
    }
    _render_w14b_html(run_meta, results, payloads, rep, tmp_path)
    html = (tmp_path / "index.html").read_text()

    # img embed only for success row (FPa). FPb (api_call_failed) and FPc
    # (target_excluded) must not get <img src=...> tags.
    assert "<img src=\"png/FPa.png\"" in html
    assert "<img src=\"png/FPb.png\"" not in html
    assert "<img src=\"png/FPc.png\"" not in html

    # All three fp_ids must still appear as rows in the results table.
    for fp_id in ("FPa", "FPb", "FPc"):
        assert fp_id in html
    # Status enums visible.
    for status in ("success", "api_call_failed", "target_excluded"):
        assert status in html

    # First-screen ordering: results table before invariants, raw JSON in <details>.
    results_pos = html.find("Per-fp actual generation results")
    inv_pos = html.find("Invariants")
    assert results_pos > 0 and inv_pos > results_pos
    raw_pos = html.find("raw run_meta")
    details_pos = html.rfind("<details>", 0, raw_pos)
    assert details_pos > 0 and details_pos < raw_pos

    # Structural methodology grep on the source script. Tokens assembled
    # per-char so this assertion source does not match its own regex.
    script_path = _SCRIPTS_DIR / "experiment_actual_floor_plan_generation_slice.py"
    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",
    ]
    pattern = re.compile(r"(?i)\b(" + "|".join(forbidden_tokens) + r")\b")
    m = pattern.search(script_path.read_text())
    assert m is None, f"scenario-specific token leaked into script: {m.group(0) if m else ''}"


def test_w14b_to_jsonable_handles_nested_pydantic_like_objects():
    """`_to_jsonable` converts nested pydantic-style BaseModel objects
    (with .model_dump or .__dict__) into JSON-serializable structures. The
    real failure mode was OpenAI SDK's response.usage carrying
    `UsageInputTokensDetails` (a nested BaseModel) that vars()/__dict__ alone
    could not flatten.
    """
    from experiment_actual_floor_plan_generation_slice import _to_jsonable

    class NestedBM:
        """Pydantic-style: exposes model_dump and __dict__."""
        def __init__(self):
            self.input_tokens = 10
            self.input_tokens_details = InnerDetails()

        def model_dump(self, mode=None):  # noqa: ARG002
            return {
                "input_tokens": self.input_tokens,
                "input_tokens_details": self.input_tokens_details.model_dump(),
            }

    class InnerDetails:
        def __init__(self):
            self.cached_tokens = 3
            self.misc = OpaqueOnlyDict()

        def model_dump(self, mode=None):  # noqa: ARG002
            return {"cached_tokens": self.cached_tokens, "misc": vars(self.misc)}

    class OpaqueOnlyDict:
        """No model_dump, only __dict__."""
        def __init__(self):
            self.flag = True

    class UnconvertibleObject:
        """No model_dump, no __dict__ usable, falls back to str()."""
        __slots__ = ()
        def __repr__(self):  # noqa: D401
            return "<unconvertible>"

    raw = {
        "usage": NestedBM(),
        "tuple_path": (Path("/abs/path"), 1, 2),
        "set_of_ints": {1, 2, 3},
        "opaque": UnconvertibleObject(),
        "primitive": "ok",
    }
    converted = _to_jsonable(raw)
    # Must round-trip through json.dumps without TypeError.
    text = json.dumps(converted, ensure_ascii=False)
    decoded = json.loads(text)
    assert decoded["usage"]["input_tokens"] == 10
    assert decoded["usage"]["input_tokens_details"]["cached_tokens"] == 3
    assert decoded["usage"]["input_tokens_details"]["misc"]["flag"] is True
    assert decoded["primitive"] == "ok"
    # Path str + tuple → list.
    assert decoded["tuple_path"][0] == "/abs/path"
    assert decoded["tuple_path"][1:] == [1, 2]
    # Set → sorted list of primitives (order-stable).
    assert sorted(decoded["set_of_ints"]) == [1, 2, 3]
    # Unconvertible object → its str() form.
    assert decoded["opaque"] == "<unconvertible>"


def test_w14b_reuse_existing_png_mode_copies_without_api_call(tmp_path):
    """reuse mode: PNG is copied from a prior run dir, status='success',
    source_mode='reused_existing_png', api_call_attempt_count=0,
    image_generation_count=0, reused_image_count incremented. No openai
    import / no real API call."""
    from experiment_actual_floor_plan_generation_slice import (
        _build_results, _build_w14b_compatibility_report, _render_w14b_html,
    )

    # Seed a prior W14b run with a fake PNG for FPa only.
    prior_run = tmp_path / "prior_run"
    prior_png_dir = prior_run / "png"
    prior_png_dir.mkdir(parents=True)
    prior_fpa = prior_png_dir / "FPa.png"
    prior_fpa.write_bytes(b"FAKEPNGBYTES_FPa_FROM_PRIOR")
    prior_size = prior_fpa.stat().st_size

    new_run = tmp_path / "new_run"
    new_run.mkdir()

    payloads = _synthetic_w14_payloads()
    effective_targets = {"FPa"}  # only one reuse target; FPb/FPc excluded

    results, counters = _build_results(
        payloads=payloads, effective_targets=effective_targets,
        mode="reuse_existing_png", run_dir=new_run,
        openai_caller=None, client=None,
        reuse_source_run_dir=prior_run,
    )

    # API call counters must stay 0; reused count == 1.
    assert counters["api_call_attempt_count"] == 0
    assert counters["image_generation_count"] == 0
    assert counters["reused_image_count"] == 1

    fpa = results["FPa"]
    assert fpa["status"] == "success"
    assert fpa["source_mode"] == "reused_existing_png"
    assert fpa["actual_api_response_meta"].get("reused_from_run") == prior_run.name
    assert fpa["actual_api_response_meta"].get("provider_metadata_unavailable") is True
    assert fpa["cost_meta"] == {}
    # PNG was actually copied to the new run dir.
    copied = new_run / "png" / "FPa.png"
    assert copied.exists()
    assert copied.stat().st_size == prior_size
    # Excluded targets remain target_excluded.
    assert results["FPb"]["status"] == "target_excluded"
    assert results["FPc"]["status"] == "target_excluded"

    # Invariant `all_targeted_results_present_and_mode_consistent` must
    # accept the reuse outcome.
    rep = _build_w14b_compatibility_report(
        payloads=payloads, effective_targets=effective_targets,
        invalid_targets=[], results=results, mode="reuse_existing_png",
        api_call_attempt_count=counters["api_call_attempt_count"],
        image_generation_count=counters["image_generation_count"],
        production_diff_empty=True, db_write_count=0,
        image_import_seen=False, image_asset_write_count=0,
        missing_inputs=[], prev_run_id="fakeW14",
    )
    assert rep["invariants"]["all_targeted_results_present_and_mode_consistent"]["pass"] is True
    assert rep["all_pass"] is True

    # Negative: reuse result missing source_mode → invariant fails.
    bad_results = json.loads(json.dumps(results))
    bad_results["FPa"]["source_mode"] = "live_api"
    rep_bad = _build_w14b_compatibility_report(
        payloads=payloads, effective_targets=effective_targets,
        invalid_targets=[], results=bad_results, mode="reuse_existing_png",
        api_call_attempt_count=0, image_generation_count=0,
        production_diff_empty=True, db_write_count=0,
        image_import_seen=False, image_asset_write_count=0,
        missing_inputs=[], prev_run_id="fakeW14",
    )
    assert rep_bad["invariants"]["all_targeted_results_present_and_mode_consistent"]["pass"] is False

    # HTML must embed the success PNG for FPa.
    run_meta = {
        "run_id": new_run.name, "stage": "w14b_actual_floor_plan_generation_slice",
        "run_status": "succeeded", "exit_code": 0,
        "derived_from": "fakeW14", "mode": "reuse_existing_png",
        "api_call_attempt_count": 0, "image_generation_count": 0,
        "reused_image_count": 1,
        "image_generation_backend": "gpt-image-2",
    }
    _render_w14b_html(run_meta, results, payloads, rep, new_run)
    html = (new_run / "index.html").read_text()
    assert "<img src=\"png/FPa.png\"" in html
    assert "<img src=\"png/FPb.png\"" not in html
    assert "<img src=\"png/FPc.png\"" not in html
