"""Behave steps for UKO Layer 2 vocabulary model and registry tests.

All step definitions use the ``for uko_l2`` suffix to avoid
AmbiguousStep collisions with other feature files.
"""

from __future__ import annotations

from _uko_l2_test_helpers import capture_error
from behave import given, then, when
from behave.runner import Context
from pydantic import ValidationError

from cleveragents.acms.uko.vocabularies import (
    VocabularyClass,
    VocabularyProperty,
    get_func_vocabulary,
    get_oo_vocabulary,
    get_proc_vocabulary,
)

# ---------------------------------------------------------------------------
# VocabularyClass -- construction
# ---------------------------------------------------------------------------


@given('a simple vocabulary class with uri "{uri}" and label "{label}" for uko_l2')
def step_create_vocab_class(ctx: Context, uri: str, label: str) -> None:
    ctx.vocab_class = VocabularyClass(uri=uri, label=label)


@given(
    'a vocabulary class with uri "{uri}" and label "{label}" '
    'and parents "{parents}" for uko_l2'
)
def step_create_vocab_class_parents(
    ctx: Context, uri: str, label: str, parents: str
) -> None:
    parent_uris = tuple(p.strip() for p in parents.split(","))
    ctx.vocab_class = VocabularyClass(uri=uri, label=label, parent_uris=parent_uris)


@then('the vocabulary class uri should be "{expected}" for uko_l2')
def step_check_vocab_class_uri(ctx: Context, expected: str) -> None:
    assert ctx.vocab_class.uri == expected


@then('the vocabulary class label should be "{expected}" for uko_l2')
def step_check_vocab_class_label(ctx: Context, expected: str) -> None:
    assert ctx.vocab_class.label == expected


@then("the vocabulary class comment should be empty for uko_l2")
def step_check_vocab_class_comment_empty(ctx: Context) -> None:
    assert ctx.vocab_class.comment == ""


@then("the vocabulary class parent_uris should be empty for uko_l2")
def step_check_vocab_class_parents_empty(ctx: Context) -> None:
    assert ctx.vocab_class.parent_uris == ()


@then("the vocabulary class should have {count:d} parent URIs for uko_l2")
def step_check_vocab_class_parent_count(ctx: Context, count: int) -> None:
    assert len(ctx.vocab_class.parent_uris) == count


@then('the vocabulary class parent {index:d} should be "{expected}" for uko_l2')
def step_check_vocab_class_parent_idx(ctx: Context, index: int, expected: str) -> None:
    assert ctx.vocab_class.parent_uris[index] == expected


# ---------------------------------------------------------------------------
# VocabularyClass -- validation
# ---------------------------------------------------------------------------


@when("I create a vocabulary class with empty uri for uko_l2")
def step_create_vocab_class_empty_uri(ctx: Context) -> None:
    with capture_error(ctx, ValidationError):
        VocabularyClass(uri="", label="Test")


@when("I create a vocabulary class with whitespace uri for uko_l2")
def step_create_vocab_class_whitespace_uri(ctx: Context) -> None:
    with capture_error(ctx, ValidationError):
        VocabularyClass(uri="   ", label="Test")


@when("I create a vocabulary class with empty label for uko_l2")
def step_create_vocab_class_empty_label(ctx: Context) -> None:
    with capture_error(ctx, ValidationError):
        VocabularyClass(uri="http://example.org/test", label="")


@then("a validation error should be raised for uko_l2")
def step_check_validation_error(ctx: Context) -> None:
    assert ctx.error is not None, "Expected a validation error but none was raised"
    assert isinstance(ctx.error, (ValidationError, ValueError))


# ---------------------------------------------------------------------------
# VocabularyClass -- frozen
# ---------------------------------------------------------------------------


@when("I try to mutate the vocabulary class uri for uko_l2")
def step_mutate_vocab_class(ctx: Context) -> None:
    with capture_error(ctx, ValidationError):
        ctx.vocab_class.uri = "http://changed"  # type: ignore[misc]  # deliberate mutation to test frozen model


@then("a frozen mutation error should be raised for uko_l2")
def step_check_frozen_error(ctx: Context) -> None:
    assert ctx.error is not None, "Expected a frozen mutation error"


# ---------------------------------------------------------------------------
# VocabularyProperty -- construction
# ---------------------------------------------------------------------------


@given(
    'a basic vocabulary property with uri "{uri}" and label "{label}" '
    'and domain "{domain}" and range "{range_uri}" for uko_l2'
)
def step_create_vocab_property(
    ctx: Context, uri: str, label: str, domain: str, range_uri: str
) -> None:
    ctx.vocab_property = VocabularyProperty(
        uri=uri, label=label, domain_uri=domain, range_uri=range_uri
    )


@given(
    'a vocabulary property with uri "{uri}" and label "{label}" '
    'and domain "{domain}" and range "{range_uri}" '
    'and sub_property_of "{sub_prop}" for uko_l2'
)
def step_create_vocab_property_sub(
    ctx: Context,
    uri: str,
    label: str,
    domain: str,
    range_uri: str,
    sub_prop: str,
) -> None:
    ctx.vocab_property = VocabularyProperty(
        uri=uri,
        label=label,
        domain_uri=domain,
        range_uri=range_uri,
        sub_property_of=sub_prop,
    )


@then('the vocabulary property uri should be "{expected}" for uko_l2')
def step_check_vocab_prop_uri(ctx: Context, expected: str) -> None:
    assert ctx.vocab_property.uri == expected


@then('the vocabulary property label should be "{expected}" for uko_l2')
def step_check_vocab_prop_label(ctx: Context, expected: str) -> None:
    assert ctx.vocab_property.label == expected


@then("the vocabulary property sub_property_of should be empty for uko_l2")
def step_check_vocab_prop_sub_empty(ctx: Context) -> None:
    assert ctx.vocab_property.sub_property_of is None


@then('the vocabulary property sub_property_of should be "{expected}" for uko_l2')
def step_check_vocab_prop_sub(ctx: Context, expected: str) -> None:
    assert ctx.vocab_property.sub_property_of == expected


@when("I create a vocabulary property with empty uri for uko_l2")
def step_create_vocab_prop_empty_uri(ctx: Context) -> None:
    with capture_error(ctx, ValidationError):
        VocabularyProperty(
            uri="",
            label="test",
            domain_uri="https://example.com/d",
            range_uri="https://example.com/r",
        )


@when("I try to mutate the vocabulary property uri for uko_l2")
def step_mutate_vocab_property(ctx: Context) -> None:
    with capture_error(ctx, ValidationError):
        ctx.vocab_property.uri = "http://changed"  # type: ignore[misc]  # deliberate mutation to test frozen model


# ---------------------------------------------------------------------------
# Paradigm vocabularies -- OO, Func, Proc
# ---------------------------------------------------------------------------


@given("the uko-oo vocabulary for uko_l2")
def step_get_oo_vocab(ctx: Context) -> None:
    ctx.vocabulary = get_oo_vocabulary()


@given("the uko-func vocabulary for uko_l2")
def step_get_func_vocab(ctx: Context) -> None:
    ctx.vocabulary = get_func_vocabulary()


@given("the uko-proc vocabulary for uko_l2")
def step_get_proc_vocab(ctx: Context) -> None:
    ctx.vocabulary = get_proc_vocabulary()


@then('the vocabulary should have prefix "{expected}" for uko_l2')
def step_check_vocab_prefix(ctx: Context, expected: str) -> None:
    assert ctx.vocabulary.prefix == expected


@then('the vocabulary should have IRI "{expected}" for uko_l2')
def step_check_vocab_iri(ctx: Context, expected: str) -> None:
    assert ctx.vocabulary.iri == expected


@then("the vocabulary should have layer {layer:d} for uko_l2")
def step_check_vocab_layer(ctx: Context, layer: int) -> None:
    assert ctx.vocabulary.layer == layer


@then("the vocabulary should have {count:d} classes for uko_l2")
def step_check_vocab_class_count(ctx: Context, count: int) -> None:
    assert len(ctx.vocabulary.classes) == count


@then("the vocabulary should have {count:d} properties for uko_l2")
def step_check_vocab_prop_count(ctx: Context, count: int) -> None:
    assert len(ctx.vocabulary.properties) == count


@then('the vocabulary should contain class "{name}" for uko_l2')
def step_check_vocab_has_class(ctx: Context, name: str) -> None:
    found = ctx.vocabulary.get_class(name)
    assert found is not None, f"Class '{name}' not found"


@then('the vocabulary should contain property "{name}" for uko_l2')
def step_check_vocab_has_property(ctx: Context, name: str) -> None:
    found = ctx.vocabulary.get_property(name)
    assert found is not None, f"Property '{name}' not found"


@when('I look up class "{name}" for uko_l2')
def step_lookup_class(ctx: Context, name: str) -> None:
    ctx.looked_up_class = ctx.vocabulary.get_class(name)


@when('I look up property "{name}" for uko_l2')
def step_lookup_property(ctx: Context, name: str) -> None:
    ctx.looked_up_property = ctx.vocabulary.get_property(name)


@then('the class should have parent "{expected}" for uko_l2')
def step_check_class_parent(ctx: Context, expected: str) -> None:
    assert ctx.looked_up_class is not None, "Class was not looked up"
    assert expected in ctx.looked_up_class.parent_uris, (
        f"Expected parent '{expected}' not found in {ctx.looked_up_class.parent_uris}"
    )


@then('the property domain should be "{expected}" for uko_l2')
def step_check_prop_domain(ctx: Context, expected: str) -> None:
    assert ctx.looked_up_property is not None
    assert ctx.looked_up_property.domain_uri == expected


@then('the property range should be "{expected}" for uko_l2')
def step_check_prop_range(ctx: Context, expected: str) -> None:
    assert ctx.looked_up_property is not None
    assert ctx.looked_up_property.range_uri == expected


@then('the property sub_property_of should be "{expected}" for uko_l2')
def step_check_prop_sub_property(ctx: Context, expected: str) -> None:
    assert ctx.looked_up_property is not None
    assert ctx.looked_up_property.sub_property_of == expected


@then("the looked up class should not be None for uko_l2")
def step_class_not_none(ctx: Context) -> None:
    assert ctx.looked_up_class is not None


@then("the looked up class should be None for uko_l2")
def step_class_is_none(ctx: Context) -> None:
    assert ctx.looked_up_class is None


@then('the looked up class uri should be "{expected}" for uko_l2')
def step_check_looked_up_class_uri(ctx: Context, expected: str) -> None:
    assert ctx.looked_up_class is not None
    assert ctx.looked_up_class.uri == expected


@then("the looked up property should not be None for uko_l2")
def step_prop_not_none(ctx: Context) -> None:
    assert ctx.looked_up_property is not None


@then("the looked up property should be None for uko_l2")
def step_prop_is_none(ctx: Context) -> None:
    assert ctx.looked_up_property is None


@then("class_uris should have {count:d} entries for uko_l2")
def step_check_class_uris_count(ctx: Context, count: int) -> None:
    assert len(ctx.vocabulary.class_uris()) == count


@then("property_uris should have {count:d} entries for uko_l2")
def step_check_property_uris_count(ctx: Context, count: int) -> None:
    assert len(ctx.vocabulary.property_uris()) == count


# ---------------------------------------------------------------------------
# Review findings: M8 — empty label/domain/range rejection
# ---------------------------------------------------------------------------


@when("I create a vocabulary property with empty label for uko_l2")
def step_create_vocab_prop_empty_label(ctx: Context) -> None:
    with capture_error(ctx, ValidationError):
        VocabularyProperty(
            uri="https://example.com/prop",
            label="",
            domain_uri="https://example.com/d",
            range_uri="https://example.com/r",
        )


@when("I create a vocabulary property with empty domain for uko_l2")
def step_create_vocab_prop_empty_domain(ctx: Context) -> None:
    with capture_error(ctx, ValidationError):
        VocabularyProperty(
            uri="https://example.com/prop",
            label="test",
            domain_uri="",
            range_uri="https://example.com/r",
        )


@when("I create a vocabulary property with empty range for uko_l2")
def step_create_vocab_prop_empty_range(ctx: Context) -> None:
    with capture_error(ctx, ValidationError):
        VocabularyProperty(
            uri="https://example.com/prop",
            label="test",
            domain_uri="https://example.com/d",
            range_uri="",
        )


# ---------------------------------------------------------------------------
# Review findings: M9 — invalid URI scheme rejection
# ---------------------------------------------------------------------------


@when("I create a vocabulary class with non-http uri for uko_l2")
def step_create_vocab_class_non_http(ctx: Context) -> None:
    with capture_error(ctx, ValidationError):
        VocabularyClass(
            uri="ftp://example.com/BadClass",
            label="BadClass",
        )


@when("I create a vocabulary property with non-http uri for uko_l2")
def step_create_vocab_prop_non_http(ctx: Context) -> None:
    with capture_error(ctx, ValidationError):
        VocabularyProperty(
            uri="urn:example:bad",
            label="bad",
            domain_uri="https://example.com/d",
            range_uri="https://example.com/r",
        )


@when("I create a vocabulary class with non-http parent uri for uko_l2")
def step_create_vocab_class_non_http_parent(ctx: Context) -> None:
    with capture_error(ctx, ValidationError):
        VocabularyClass(
            uri="https://example.com/Good",
            label="Good",
            parent_uris=("ftp://example.com/BadParent",),
        )


@when("I create a vocabulary property with non-http domain uri for uko_l2")
def step_create_vocab_prop_non_http_domain(ctx: Context) -> None:
    with capture_error(ctx, ValidationError):
        VocabularyProperty(
            uri="https://example.com/prop",
            label="prop",
            domain_uri="ftp://example.com/BadDomain",
            range_uri="https://example.com/r",
        )


@when("I create a vocabulary property with non-http range uri for uko_l2")
def step_create_vocab_prop_non_http_range(ctx: Context) -> None:
    with capture_error(ctx, ValidationError):
        VocabularyProperty(
            uri="https://example.com/prop",
            label="prop",
            domain_uri="https://example.com/d",
            range_uri="file:///etc/passwd",
        )


@when("I create a vocabulary property with non-http sub_property_of for uko_l2")
def step_create_vocab_prop_non_http_subprop(ctx: Context) -> None:
    with capture_error(ctx, ValidationError):
        VocabularyProperty(
            uri="https://example.com/prop",
            label="prop",
            domain_uri="https://example.com/d",
            range_uri="https://example.com/r",
            sub_property_of="javascript:alert(1)",
        )
