"""WorkflowSpec validation — the AI-builder contract (plan §5-§6).

The errors ARE the UX: every problem reported at once, each naming its step,
with near-match suggestions for probable typos.
"""

import json
from pathlib import Path

import pytest

from app.schemas import SpecError, validate_spec

SEEDS = Path(__file__).resolve().parent.parent / "app" / "seeds" / "workflows"


def _spec(**over):
    base = {
        "key": "t", "title": "T", "start": "a",
        "variables": {"who": {"type": "email_list", "label": "Who"}},
        "steps": [
            {"id": "a", "owner": "client", "headline": "Do the thing",
             "primary": {"label": "Done", "to": "b"}},
            {"id": "b", "owner": "team", "headline": "Verify",
             "primary": {"label": "Confirmed", "to": "$done"}},
        ],
    }
    base.update(over)
    return base


def _errors(raw):
    with pytest.raises(SpecError) as exc:
        validate_spec(raw)
    return exc.value.errors


def test_both_seed_files_validate():
    for f in sorted(SEEDS.glob("*.json")):
        validate_spec(json.loads(f.read_text()))


def test_unknown_transition_suggests_near_match():
    raw = _spec()
    raw["steps"][0]["primary"]["to"] = "bb"
    errs = _errors(raw)
    assert any("unknown step 'bb'" in e and "did you mean 'b'" in e for e in errs)


def test_all_errors_reported_at_once():
    raw = _spec()
    raw["start"] = "nope"
    raw["steps"][0]["primary"]["to"] = "zz"
    raw["steps"][0]["headline"] = "Hi {{whoo}}"
    errs = _errors(raw)
    assert len(errs) >= 3
    assert any("start step 'nope'" in e for e in errs)
    assert any("whoo" in e and "did you mean 'who'" in e for e in errs)


def test_unreachable_and_no_path_to_done():
    raw = _spec()
    raw["steps"].append({"id": "island", "owner": "client", "headline": "x",
                         "primary": {"label": "Go", "to": "island2"}})
    raw["steps"].append({"id": "island2", "owner": "client", "headline": "x",
                         "primary": {"label": "Back", "to": "island"}})
    errs = _errors(raw)
    assert any("'island' is unreachable" in e for e in errs)


def test_loop_without_done_is_refused():
    raw = _spec()
    raw["steps"][1]["primary"]["to"] = "a"     # a->b->a, no $done anywhere
    errs = _errors(raw)
    assert any("no path to $done" in e for e in errs)


def test_flag_shape_rules():
    raw = _spec()
    raw["steps"][0]["alternatives"] = [
        {"key": "x", "label": "X", "kind": "flag", "to": "b"},          # flag with to
        {"key": "y", "label": "Y", "kind": "jump"},                      # jump without to
        {"key": "z", "label": "Z", "kind": "flag", "tone": "loud"},      # bad tone
    ]
    errs = _errors(raw)
    assert any("remove 'to'" in e for e in errs)
    assert any("has no 'to'" in e for e in errs)
    assert any("needs tone" in e for e in errs)


def test_targets_must_be_declared_email_list():
    raw = _spec()
    raw["variables"]["name"] = {"type": "text", "label": "Name"}
    raw["steps"][0]["targets"] = "name"
    errs = _errors(raw)
    assert any("must be type 'email_list'" in e for e in errs)
    raw["steps"][0]["targets"] = "whoo"
    errs = _errors(raw)
    assert any("not a declared variable" in e and "did you mean 'who'" in e for e in errs)
