"""W21B-wave-4 v8 Phase A2: floor_plan_layout_plan pure validator + schema.

Path-S coordinate scaffold core (deterministic). The LLM emits an
element_layout[] of coarse rects on a normalized grid; this module
validates the shape + exact-ID join to the dossier inventory + geometric
sanity (bounds / duplicate / impossible overlap / render cap / transient
exclusion). NO LLM/image/VLM/DB I/O. Lifts the proven A-0 dry-run logic
into a production-testable contract.

Boundaries (brief §2.1 A2 + scenario-leakage guard): code checks
structure / exact-ID / geometry only; never lexically inspects labels.
"""
from __future__ import annotations

import jsonschema
import pytest

from app.modules.pipeline.floor_plan_layout_plan import (
    BASE_STRUCTURAL_LAYERS,
    DEFAULT_GRID,
    DEFAULT_RENDER_CAP,
    LAYOUT_SCHEMA,
    _OPENAI_UNSUPPORTED_SCHEMA_KEYS,
    LayoutPlanError,
    compute_synthetic_layout_fixture,
    validate_layout,
)


def _inventory():
    return [
        {"number": 1, "label": "living zone", "category": "area",
         "base_layer_decision": "base_structural_unit"},
        {"number": 2, "label": "entry door", "category": "opening",
         "base_layer_decision": "base_opening"},
        {"number": 3, "label": "low storage chest", "category": "furniture",
         "base_layer_decision": "base_persistent_furniture"},
        {"number": 4, "label": "wall sink", "category": "prop",
         "base_layer_decision": "base_persistent_fixture"},
        {"number": 9, "label": "transient floor mark", "category": "prop",
         "base_layer_decision": "state_overlay_plot_cue"},
    ]


def _el(number, layer, x, y, w, h, *, render=True, shape="rect"):
    return {
        "number": number, "label": "x", "base_layer_decision": layer,
        "importance": 0.8, "render_on_plan": render, "shape_kind": shape,
        "rect": {"x": x, "y": y, "w": w, "h": h},
        "adjacent_to": [], "camera_visibility_hint": "midground",
    }


def _good():
    return {
        "fp_id": "fp_a",
        "place_semantic_tags": ["compact dwelling"],
        "expected_visual_density": "low",
        "key_fixture_groups": [{"group_id": "svc", "label": "service strip"}],
        "element_layout": [
            _el(1, "base_structural_unit", 0, 0, 60, 60, shape="area"),
            _el(2, "base_opening", 0, 58, 10, 4),
            _el(3, "base_persistent_furniture", 5, 5, 20, 10),
            _el(4, "base_persistent_fixture", 70, 10, 12, 8),
        ],
        "metadata_only_elements": [],
        "validation_notes": [],
    }


# ── schema ──
def test_schema_no_openai_unsupported_keys():
    def walk(n):
        if isinstance(n, dict):
            for k, v in n.items():
                assert k not in _OPENAI_UNSUPPORTED_SCHEMA_KEYS, k
                walk(v)
        elif isinstance(n, list):
            for x in n:
                walk(x)
    walk(LAYOUT_SCHEMA)


def test_good_validates_against_jsonschema():
    jsonschema.validate(instance=_good(), schema=LAYOUT_SCHEMA)


# ── validator happy ──
def test_validator_passes_well_formed():
    res = validate_layout(output=_good(), dossier_inventory=_inventory(), fp_id="fp_a")
    assert res["ok"] is True, res["blockers"]
    assert res["layout"]["fp_id"] == "fp_a"


# ── exact-ID / classification ──
def test_rejects_unknown_marker():
    out = _good()
    out["element_layout"][0]["number"] = 99
    res = validate_layout(output=out, dossier_inventory=_inventory(), fp_id="fp_a")
    assert res["ok"] is False
    assert any("99" in b for b in res["blockers"])


def test_rejects_reclassified_layer():
    out = _good()
    out["element_layout"][2]["base_layer_decision"] = "base_opening"
    res = validate_layout(output=out, dossier_inventory=_inventory(), fp_id="fp_a")
    assert res["ok"] is False
    assert any("base_layer_decision" in b for b in res["blockers"])


def test_rejects_transient_in_layout():
    out = _good()
    out["element_layout"].append(_el(9, "state_overlay_plot_cue", 80, 80, 5, 5))
    res = validate_layout(output=out, dossier_inventory=_inventory(), fp_id="fp_a")
    assert res["ok"] is False
    assert any("state_overlay" in b or "transient" in b.lower() for b in res["blockers"])


def test_rejects_fp_id_mismatch():
    res = validate_layout(output=_good(), dossier_inventory=_inventory(), fp_id="other")
    assert res["ok"] is False


# ── geometry ──
def test_rejects_out_of_bounds():
    out = _good()
    out["element_layout"][3]["rect"] = {"x": 95, "y": 10, "w": 20, "h": 8}
    res = validate_layout(output=out, dossier_inventory=_inventory(), fp_id="fp_a")
    assert res["ok"] is False
    assert any("bound" in b.lower() for b in res["blockers"])


def test_rejects_duplicate_number():
    out = _good()
    out["element_layout"][1]["number"] = 1
    out["element_layout"][1]["base_layer_decision"] = "base_structural_unit"
    res = validate_layout(output=out, dossier_inventory=_inventory(), fp_id="fp_a")
    assert res["ok"] is False
    assert any("dup" in b.lower() for b in res["blockers"])


def test_rejects_impossible_overlap_between_nonarea():
    out = _good()
    # marker 4 (fixture) placed on top of marker 3 (furniture) ~fully
    out["element_layout"][3]["rect"] = dict(out["element_layout"][2]["rect"])
    res = validate_layout(output=out, dossier_inventory=_inventory(), fp_id="fp_a")
    assert res["ok"] is False
    assert any("overlap" in b.lower() for b in res["blockers"])


def test_area_containment_not_overlap_error():
    # furniture inside the structural-unit area must NOT be an overlap error
    res = validate_layout(output=_good(), dossier_inventory=_inventory(), fp_id="fp_a")
    assert res["ok"] is True, res["blockers"]


def test_rejects_render_cap_exceeded():
    inv = [{"number": n, "label": "f", "category": "furniture",
            "base_layer_decision": "base_persistent_furniture"}
           for n in range(1, DEFAULT_RENDER_CAP + 6)]
    els = []
    for i, e in enumerate(inv):
        x = (i % 9) * 10
        y = (i // 9) * 10
        els.append(_el(e["number"], "base_persistent_furniture", x, y, 6, 6))
    out = {"fp_id": "fp_a", "place_semantic_tags": [], "expected_visual_density": "high",
           "key_fixture_groups": [], "element_layout": els,
           "metadata_only_elements": [], "validation_notes": []}
    res = validate_layout(output=out, dossier_inventory=inv, fp_id="fp_a")
    assert res["ok"] is False
    assert any("cap" in b.lower() for b in res["blockers"])


def test_metadata_only_elements_not_rendered_ok():
    out = _good()
    # demote marker 4 to metadata_only (not in element_layout)
    out["element_layout"] = out["element_layout"][:3]
    out["metadata_only_elements"] = [{"number": 4, "label": "wall sink"}]
    res = validate_layout(output=out, dossier_inventory=_inventory(), fp_id="fp_a")
    assert res["ok"] is True, res["blockers"]


# ── synthetic fixture ──
def test_synthetic_fixture_shape():
    fx = compute_synthetic_layout_fixture(dossier_inventory=_inventory(), fp_id="fp_a")
    assert fx["fp_id"] == "fp_a"
    # only base_* markers placed; transient excluded
    nums = {e["number"] for e in fx["element_layout"]}
    assert 9 not in nums
    # synthetic fixture must itself validate
    res = validate_layout(output=fx, dossier_inventory=_inventory(), fp_id="fp_a")
    assert res["ok"] is True, res["blockers"]


def test_synthetic_fixture_raises_on_empty_inventory():
    with pytest.raises(LayoutPlanError):
        compute_synthetic_layout_fixture(dossier_inventory=[], fp_id="fp_a")


def test_enum_exports():
    assert BASE_STRUCTURAL_LAYERS == {
        "base_structural_unit", "base_opening",
        "base_persistent_fixture", "base_persistent_furniture"}
    assert DEFAULT_GRID == 100
