"""Step definitions for features/uko_layer3_vocabularies.feature (detail level map scenarios).

Steps for UKO Layer 3 -- detail level maps and verification.
All steps use the suffix "for uko_l3" to avoid AmbiguousStep errors.
"""

from __future__ import annotations

from typing import Any

from behave import given, then, when

from cleveragents.acms.uko import (
    JAVA_VOCABULARY as _JAVA_VOCABULARY_import,
)
from cleveragents.acms.uko import (
    PYTHON_VOCABULARY as _PYTHON_VOCABULARY_import,
)
from cleveragents.acms.uko import (
    RUST_VOCABULARY as _RUST_VOCABULARY_import,
)
from cleveragents.acms.uko import (
    TYPESCRIPT_VOCABULARY as _TYPESCRIPT_VOCABULARY_import,
)
from cleveragents.acms.uko import (
    ProvenanceInfo as _ProvenanceInfo_import,
)
from cleveragents.acms.uko import (
    build_detail_level_map as _build_detail_level_map_import,
)
from cleveragents.acms.uko import (
    resolve_detail_level as _resolve_detail_level_import,
)
from cleveragents.acms.uko.layer3_java import JAVA_DETAIL_LEVELS
from cleveragents.acms.uko.layer3_py import (
    PYTHON_DETAIL_LEVELS,
    PYTHON_VOCABULARY,
)
from cleveragents.acms.uko.layer3_rs import (
    RUST_DETAIL_LEVELS,
    RUST_VOCABULARY,
)
from cleveragents.acms.uko.layer3_ts import TYPESCRIPT_DETAIL_LEVELS
from cleveragents.acms.uko.vocabulary import (
    ProvenanceInfo,
    build_detail_level_map,
    resolve_detail_level,
)

__all__: list[str] = []


# ===========================================================================
# Helpers
# ===========================================================================


def _parse_entries(entries_str: str) -> tuple[tuple[str, int], ...]:
    """Parse "A=0,B=1,C=2" into tuple of (name, depth) pairs."""
    result: list[tuple[str, int]] = []
    for pair in entries_str.split(","):
        name, val = pair.strip().split("=")
        result.append((name.strip(), int(val.strip())))
    return tuple(result)


# ===========================================================================
# build_detail_level_map steps
# ===========================================================================


@given('a parent level map with entries "{entries}" for uko_l3')
def step_given_parent_map(context: Any, entries: str) -> None:
    context.parent_levels = _parse_entries(entries)


@when("I build a detail level map with no insertions for uko_l3")
def step_when_build_no_insertions(context: Any) -> None:
    context.result_map = build_detail_level_map(context.parent_levels, ())


@when('I build a detail level map with insertion "{entry}" for uko_l3')
def step_when_build_one_insertion(context: Any, entry: str) -> None:
    insertions = _parse_entries(entry)
    context.result_map = build_detail_level_map(context.parent_levels, insertions)


@when('I build a detail level map with insertions "{entries}" for uko_l3')
def step_when_build_multiple_insertions(context: Any, entries: str) -> None:
    insertions = _parse_entries(entries)
    context.result_map = build_detail_level_map(context.parent_levels, insertions)


@then("the resulting map should have {count:d} entries for uko_l3")
def step_then_result_map_count(context: Any, count: int) -> None:
    assert len(context.result_map) == count


@then('the resulting map entry "{name}" should have depth {depth:d} for uko_l3')
def step_then_result_map_entry(context: Any, name: str, depth: int) -> None:
    level_dict = dict(context.result_map)
    assert name in level_dict, f"{name!r} not in {level_dict}"
    assert level_dict[name] == depth, f"Expected {name}={depth}, got {level_dict[name]}"


@then(
    "building a detail level map with duplicate insertion "
    '"{entry}" should raise ValueError for uko_l3'
)
def step_then_build_duplicate_raises(context: Any, entry: str) -> None:
    insertions = _parse_entries(entry)
    try:
        build_detail_level_map(context.parent_levels, insertions)
        msg = "Expected ValueError for duplicate names"
        raise AssertionError(msg)
    except ValueError:
        pass


# ===========================================================================
# Python DetailLevelMap steps
# ===========================================================================


@then("the Python detail level map should have {count:d} entries for uko_l3")
def step_then_py_map_count(context: Any, count: int) -> None:
    assert len(PYTHON_DETAIL_LEVELS) == count


@then('the Python detail level "{name}" should resolve to depth {depth:d} for uko_l3')
def step_then_py_level_depth(context: Any, name: str, depth: int) -> None:
    resolved = resolve_detail_level(name, PYTHON_DETAIL_LEVELS)
    assert resolved == depth, f"Expected {name}={depth}, got {resolved}"


# ===========================================================================
# resolve_detail_level steps
# ===========================================================================


@given('a detail level map with entries "{entries}" for uko_l3')
def step_given_detail_map(context: Any, entries: str) -> None:
    context.detail_levels = _parse_entries(entries)


@given('a parent detail level map with entries "{entries}" for uko_l3')
def step_given_parent_detail_map(context: Any, entries: str) -> None:
    context.parent_detail_levels = _parse_entries(entries)


@then('resolving "{name}" should return {depth:d} for uko_l3')
def step_then_resolve_name(context: Any, name: str, depth: int) -> None:
    resolved = resolve_detail_level(name, context.detail_levels)
    assert resolved == depth


@then('resolving "{name}" with parent fallback should return {depth:d} for uko_l3')
def step_then_resolve_with_parent(context: Any, name: str, depth: int) -> None:
    resolved = resolve_detail_level(
        name, context.detail_levels, context.parent_detail_levels
    )
    assert resolved == depth


@then('resolving "{name}" with parent fallback should raise ValueError for uko_l3')
def step_then_resolve_with_parent_raises(context: Any, name: str) -> None:
    try:
        resolve_detail_level(name, context.detail_levels, context.parent_detail_levels)
        msg = "Expected ValueError"
        raise AssertionError(msg)
    except ValueError:
        pass


@then('resolving "{name}" should raise ValueError for uko_l3')
def step_then_resolve_raises(context: Any, name: str) -> None:
    try:
        resolve_detail_level(name, context.detail_levels)
        msg = "Expected ValueError"
        raise AssertionError(msg)
    except ValueError:
        pass


@then("resolving empty string should raise ValueError for uko_l3")
def step_then_resolve_empty_raises(context: Any) -> None:
    try:
        resolve_detail_level("", context.detail_levels)
        msg = "Expected ValueError"
        raise AssertionError(msg)
    except ValueError:
        pass


@then("resolving whitespace string should raise ValueError for uko_l3")
def step_then_resolve_whitespace_raises(context: Any) -> None:
    try:
        resolve_detail_level("   ", context.detail_levels)
        msg = "Expected ValueError"
        raise AssertionError(msg)
    except ValueError:
        pass


# ===========================================================================
# 4-layer chain resolution steps
# ===========================================================================


@given('a layer 0 map with "{entries}" for uko_l3')
def step_given_layer0_map(context: Any, entries: str) -> None:
    context.layer0_map = _parse_entries(entries)


@given('a layer 1 map with "{entries}" inheriting layer 0 for uko_l3')
def step_given_layer1_map(context: Any, entries: str) -> None:
    context.layer1_map = _parse_entries(entries)


@given('a layer 2 map with "{entries}" inheriting layer 1 for uko_l3')
def step_given_layer2_map(context: Any, entries: str) -> None:
    context.layer2_map = _parse_entries(entries)


@given('a layer 3 map with "{entries}" inheriting layer 2 for uko_l3')
def step_given_layer3_map(context: Any, entries: str) -> None:
    context.layer3_map = _parse_entries(entries)


def _resolve_through_chain(
    name: str,
    layer_maps: tuple[tuple[tuple[str, int], ...], ...],
) -> int:
    """Resolve *name* through a chain of maps using ``resolve_detail_level``.

    Walks the chain from most-specific (index 0) to least-specific,
    calling ``resolve_detail_level`` with the current map and the next
    map as the parent fallback.  The last map has no parent.
    """
    for idx, current_map in enumerate(layer_maps):
        parent = layer_maps[idx + 1] if idx + 1 < len(layer_maps) else None
        try:
            return resolve_detail_level(name, current_map, parent)
        except ValueError:
            # Not found in current + parent -- continue to next pair.
            continue
    raise ValueError(f"Unknown detail level: {name!r}")


@then('resolving "{name}" through layer 3 should return {depth:d} for uko_l3')
def step_then_resolve_through_chain(context: Any, name: str, depth: int) -> None:
    chain = (
        context.layer3_map,
        context.layer2_map,
        context.layer1_map,
        context.layer0_map,
    )
    resolved = _resolve_through_chain(name, chain)
    assert resolved == depth, f"Expected {name}={depth}, got {resolved}"


@then('resolving "{name}" through layer 3 should raise ValueError for uko_l3')
def step_then_resolve_chain_raises(context: Any, name: str) -> None:
    chain = (
        context.layer3_map,
        context.layer2_map,
        context.layer1_map,
        context.layer0_map,
    )
    try:
        _resolve_through_chain(name, chain)
        msg = f"Expected ValueError for {name!r}"
        raise AssertionError(msg)
    except ValueError:
        pass


# ===========================================================================
# TypeScript / Rust / Java DetailLevelMap steps
# ===========================================================================


@then("the TypeScript detail level map should have {count:d} entries for uko_l3")
def step_then_ts_map_count(context: Any, count: int) -> None:
    assert len(TYPESCRIPT_DETAIL_LEVELS) == count


@then(
    'the TypeScript detail level "{name}" should resolve to depth {depth:d} for uko_l3'
)
def step_then_ts_level_depth(context: Any, name: str, depth: int) -> None:
    resolved = resolve_detail_level(name, TYPESCRIPT_DETAIL_LEVELS)
    assert resolved == depth


@then("the Rust detail level map should have {count:d} entries for uko_l3")
def step_then_rs_map_count(context: Any, count: int) -> None:
    assert len(RUST_DETAIL_LEVELS) == count


@then('the Rust detail level "{name}" should resolve to depth {depth:d} for uko_l3')
def step_then_rs_level_depth(context: Any, name: str, depth: int) -> None:
    resolved = resolve_detail_level(name, RUST_DETAIL_LEVELS)
    assert resolved == depth


@then("the Java detail level map should have {count:d} entries for uko_l3")
def step_then_java_map_count(context: Any, count: int) -> None:
    assert len(JAVA_DETAIL_LEVELS) == count


@then('the Java detail level "{name}" should resolve to depth {depth:d} for uko_l3')
def step_then_java_level_depth(context: Any, name: str, depth: int) -> None:
    resolved = resolve_detail_level(name, JAVA_DETAIL_LEVELS)
    assert resolved == depth


# ===========================================================================
# Provenance contract steps
# ===========================================================================


@then('ProvenanceInfo should have field "{field}" for uko_l3')
def step_then_provenance_has_field(context: Any, field: str) -> None:
    assert hasattr(ProvenanceInfo, "model_fields"), (
        "ProvenanceInfo is not a Pydantic model"
    )
    assert field in ProvenanceInfo.model_fields, (
        f"ProvenanceInfo missing field {field!r}"
    )


# ===========================================================================
# Layer 2 dependency steps
# ===========================================================================


@then(
    "the Python vocabulary should have at least {count:d} "
    "layer2 dependencies for uko_l3"
)
def step_then_py_layer2_dep_count(context: Any, count: int) -> None:
    assert len(PYTHON_VOCABULARY.layer2_dependencies) >= count


@then(
    "the Python vocabulary layer2 dependencies should include "
    'uri containing "{fragment}" for uko_l3'
)
def step_then_py_layer2_dep_uri(context: Any, fragment: str) -> None:
    uris = tuple(d.uri for d in PYTHON_VOCABULARY.layer2_dependencies)
    assert any(fragment in u for u in uris), (
        f"No dependency URI containing {fragment!r} in {uris}"
    )


@then(
    "the Rust vocabulary layer2 dependencies should include "
    'prefix "{prefix}" for uko_l3'
)
def step_then_rs_layer2_dep_prefix(context: Any, prefix: str) -> None:
    prefixes = tuple(d.prefix for d in RUST_VOCABULARY.layer2_dependencies)
    assert prefix in prefixes, f"{prefix!r} not in {prefixes}"


# ===========================================================================
# Package import steps
# ===========================================================================


@then("importing PythonVocabulary from acms.uko should succeed for uko_l3")
def step_then_import_python_vocab(context: Any) -> None:
    assert _PYTHON_VOCABULARY_import is not None


@then("importing TypeScriptVocabulary from acms.uko should succeed for uko_l3")
def step_then_import_ts_vocab(context: Any) -> None:
    assert _TYPESCRIPT_VOCABULARY_import is not None


@then("importing RustVocabulary from acms.uko should succeed for uko_l3")
def step_then_import_rust_vocab(context: Any) -> None:
    assert _RUST_VOCABULARY_import is not None


@then("importing JavaVocabulary from acms.uko should succeed for uko_l3")
def step_then_import_java_vocab(context: Any) -> None:
    assert _JAVA_VOCABULARY_import is not None


@then("importing ProvenanceInfo from acms.uko should succeed for uko_l3")
def step_then_import_provenance(context: Any) -> None:
    assert _ProvenanceInfo_import is not None


@then("importing build_detail_level_map from acms.uko should succeed for uko_l3")
def step_then_import_build_map(context: Any) -> None:
    assert _build_detail_level_map_import is not None


@then("importing resolve_detail_level from acms.uko should succeed for uko_l3")
def step_then_import_resolve(context: Any) -> None:
    assert _resolve_detail_level_import is not None
