"""Step definitions for the ACMS Backend Abstraction Layer feature."""

from __future__ import annotations

from dataclasses import FrozenInstanceError
from typing import Any

from behave import given, then, when

from cleveragents.application.container import get_container, reset_container
from cleveragents.domain.models.acms.backends import (
    GraphBackend,
    GraphResult,
    TextBackend,
    TextResult,
    VectorBackend,
    VectorResult,
)
from cleveragents.domain.models.acms.stubs import (
    InMemoryGraphBackend,
    InMemoryTextBackend,
    InMemoryVectorBackend,
)

__all__: list[str] = []


# ---------------------------------------------------------------------------
# TextResult steps
# ---------------------------------------------------------------------------


@given('a TextResult with uko_uri "{uko_uri}" and score {score:g}')
def step_given_text_result(context: Any, uko_uri: str, score: float) -> None:
    context.text_result = TextResult(
        uko_uri=uko_uri,
        content="class Auth: pass",
        score=score,
    )


@then('the text result uko_uri should be "{expected}"')
def step_then_text_result_uko_uri(context: Any, expected: str) -> None:
    assert context.text_result.uko_uri == expected


@then('the text result content should be "{expected}"')
def step_then_text_result_content(context: Any, expected: str) -> None:
    assert context.text_result.content == expected


@then("the text result score should be {expected:g}")
def step_then_text_result_score(context: Any, expected: float) -> None:
    assert context.text_result.score == expected


@then("the text result metadata should be empty")
def step_then_text_result_metadata_empty(context: Any) -> None:
    assert context.text_result.metadata == {}


@then("creating a TextResult with empty uko_uri should raise ValueError")
def step_then_text_result_empty_uko_uri(context: Any) -> None:
    try:
        TextResult(uko_uri="", content="x", score=0.5)
        raise AssertionError("Expected ValueError")
    except ValueError:
        pass


@then("creating a TextResult with score {score:g} should raise ValueError")
def step_then_text_result_bad_score(context: Any, score: float) -> None:
    try:
        TextResult(uko_uri="uko:x", content="x", score=score)
        raise AssertionError("Expected ValueError")
    except ValueError:
        pass


@then("modifying the text result score should raise an error")
def step_then_text_result_immutable(context: Any) -> None:
    try:
        context.text_result.score = 0.1  # type: ignore[misc]
        raise AssertionError("Expected FrozenInstanceError")
    except FrozenInstanceError:
        pass


# ---------------------------------------------------------------------------
# VectorResult steps
# ---------------------------------------------------------------------------


@given('a VectorResult with uko_uri "{uko_uri}" and score {score:g}')
def step_given_vector_result(context: Any, uko_uri: str, score: float) -> None:
    context.vector_result = VectorResult(
        uko_uri=uko_uri,
        content="vector store content",
        score=score,
    )


@then('the vector result uko_uri should be "{expected}"')
def step_then_vector_result_uko_uri(context: Any, expected: str) -> None:
    assert context.vector_result.uko_uri == expected


@then('the vector result content should be "{expected}"')
def step_then_vector_result_content(context: Any, expected: str) -> None:
    assert context.vector_result.content == expected


@then("the vector result score should be {expected:g}")
def step_then_vector_result_score(context: Any, expected: float) -> None:
    assert context.vector_result.score == expected


@then("the vector result metadata should be empty")
def step_then_vector_result_metadata_empty(context: Any) -> None:
    assert context.vector_result.metadata == {}


@then("creating a VectorResult with empty uko_uri should raise ValueError")
def step_then_vector_result_empty_uko_uri(context: Any) -> None:
    try:
        VectorResult(uko_uri="", content="x", score=0.5)
        raise AssertionError("Expected ValueError")
    except ValueError:
        pass


@then("creating a VectorResult with score {score:g} should raise ValueError")
def step_then_vector_result_bad_score(context: Any, score: float) -> None:
    try:
        VectorResult(uko_uri="uko:x", content="x", score=score)
        raise AssertionError("Expected ValueError")
    except ValueError:
        pass


@then("modifying the vector result score should raise an error")
def step_then_vector_result_immutable(context: Any) -> None:
    try:
        context.vector_result.score = 0.1  # type: ignore[misc]
        raise AssertionError("Expected FrozenInstanceError")
    except FrozenInstanceError:
        pass


# ---------------------------------------------------------------------------
# GraphResult steps
# ---------------------------------------------------------------------------


@given("a GraphResult with no triples")
def step_given_graph_result_empty(context: Any) -> None:
    context.graph_result = GraphResult()


@given('a GraphResult with triple "{s}" "{p}" "{o}"')
def step_given_graph_result_triple(context: Any, s: str, p: str, o: str) -> None:
    context.graph_result = GraphResult(triples=[(s, p, o)])


@then("the graph result triples should be empty")
def step_then_graph_result_triples_empty(context: Any) -> None:
    assert context.graph_result.triples == []


@then("the graph result metadata should be empty")
def step_then_graph_result_metadata_empty(context: Any) -> None:
    assert context.graph_result.metadata == {}


@then("the graph result should have {count:d} triple")
def step_then_graph_result_triple_count(context: Any, count: int) -> None:
    assert len(context.graph_result.triples) == count


@then("modifying the graph result metadata should raise an error")
def step_then_graph_result_immutable(context: Any) -> None:
    try:
        context.graph_result.metadata = {"x": "y"}  # type: ignore[misc]
        raise AssertionError("Expected FrozenInstanceError")
    except FrozenInstanceError:
        pass


# ---------------------------------------------------------------------------
# InMemoryTextBackend steps
# ---------------------------------------------------------------------------


@given("an InMemoryTextBackend instance")
def step_given_text_backend(context: Any) -> None:
    context.backend = InMemoryTextBackend()


@then("it should be a TextBackend")
def step_then_is_text_backend(context: Any) -> None:
    assert isinstance(context.backend, TextBackend)


@when('I search for "{query}" with scope "{scope}"')
def step_when_text_search(context: Any, query: str, scope: str) -> None:
    context.search_results = context.backend.search(query, scope=frozenset({scope}))


@when('I search for "{query}" with scope "{scope}" and max_results {n:d}')
def step_when_text_search_max(context: Any, query: str, scope: str, n: int) -> None:
    context.search_results = context.backend.search(
        query, scope=frozenset({scope}), max_results=n
    )


@then("the text search results should be empty")
def step_then_text_search_empty(context: Any) -> None:
    assert context.search_results == []


@then("searching with empty query should raise ValueError")
def step_then_text_search_empty_query(context: Any) -> None:
    try:
        context.backend.search("", scope=frozenset({"RES01"}))
        raise AssertionError("Expected ValueError")
    except ValueError:
        pass


@then("searching with max_results {n:d} should raise ValueError")
def step_then_text_search_bad_max(context: Any, n: int) -> None:
    try:
        context.backend.search("q", scope=frozenset({"RES01"}), max_results=n)
        raise AssertionError("Expected ValueError")
    except ValueError:
        pass


# ---------------------------------------------------------------------------
# InMemoryVectorBackend steps
# ---------------------------------------------------------------------------


@given("an InMemoryVectorBackend instance")
def step_given_vector_backend(context: Any) -> None:
    context.backend = InMemoryVectorBackend()


@then("it should be a VectorBackend")
def step_then_is_vector_backend(context: Any) -> None:
    assert isinstance(context.backend, VectorBackend)


@when('I similarity search with embedding and scope "{scope}"')
def step_when_vector_search(context: Any, scope: str) -> None:
    context.search_results = context.backend.similarity_search(
        [0.1, 0.2, 0.3], scope=frozenset({scope})
    )


@when('I similarity search with embedding and scope "{scope}" and top_k {k:d}')
def step_when_vector_search_topk(context: Any, scope: str, k: int) -> None:
    context.search_results = context.backend.similarity_search(
        [0.1, 0.2, 0.3], scope=frozenset({scope}), top_k=k
    )


@then("the vector search results should be empty")
def step_then_vector_search_empty(context: Any) -> None:
    assert context.search_results == []


@then("similarity searching with empty embedding should raise ValueError")
def step_then_vector_search_empty_embed(context: Any) -> None:
    try:
        context.backend.similarity_search([], scope=frozenset({"RES01"}))
        raise AssertionError("Expected ValueError")
    except ValueError:
        pass


@then("similarity searching with top_k {k:d} should raise ValueError")
def step_then_vector_search_bad_topk(context: Any, k: int) -> None:
    try:
        context.backend.similarity_search([0.1], scope=frozenset({"RES01"}), top_k=k)
        raise AssertionError("Expected ValueError")
    except ValueError:
        pass


# ---------------------------------------------------------------------------
# InMemoryGraphBackend steps
# ---------------------------------------------------------------------------


@given("an InMemoryGraphBackend instance")
def step_given_graph_backend(context: Any) -> None:
    context.backend = InMemoryGraphBackend()


@then("it should be a GraphBackend")
def step_then_is_graph_backend(context: Any) -> None:
    assert isinstance(context.backend, GraphBackend)


@when('I run sparql query "{query}" with scope "{scope}"')
def step_when_sparql_query(context: Any, query: str, scope: str) -> None:
    context.graph_query_result = context.backend.sparql_query(
        query, scope=frozenset({scope})
    )


@then("the graph query result should have no triples")
def step_then_graph_query_empty(context: Any) -> None:
    assert context.graph_query_result.triples == []


@then("sparql querying with empty query should raise ValueError")
def step_then_sparql_empty_query(context: Any) -> None:
    try:
        context.backend.sparql_query("", scope=frozenset({"RES01"}))
        raise AssertionError("Expected ValueError")
    except ValueError:
        pass


@when('I get triples for subject "{subject}"')
def step_when_get_triples(context: Any, subject: str) -> None:
    context.graph_query_result = context.backend.get_triples(subject)


@then("getting triples with empty subject should raise ValueError")
def step_then_get_triples_empty_subject(context: Any) -> None:
    try:
        context.backend.get_triples("")
        raise AssertionError("Expected ValueError")
    except ValueError:
        pass


@when('I traverse from "{start}" with depth {depth:d}')
def step_when_traverse(context: Any, start: str, depth: int) -> None:
    context.graph_query_result = context.backend.traverse(start, depth=depth)


@then("traversing with empty start should raise ValueError")
def step_then_traverse_empty_start(context: Any) -> None:
    try:
        context.backend.traverse("")
        raise AssertionError("Expected ValueError")
    except ValueError:
        pass


@then("traversing with depth {depth:d} should raise ValueError")
def step_then_traverse_bad_depth(context: Any, depth: int) -> None:
    try:
        context.backend.traverse("uko:x", depth=depth)
        raise AssertionError("Expected ValueError")
    except ValueError:
        pass


# ---------------------------------------------------------------------------
# DI Container steps
# ---------------------------------------------------------------------------


@given("the DI container")
def step_given_container(context: Any) -> None:
    reset_container()
    context.container = get_container()


@then("the container should provide a text_backend")
def step_then_container_text(context: Any) -> None:
    context.resolved_text = context.container.text_backend()
    assert context.resolved_text is not None


@then("the text_backend should be a TextBackend")
def step_then_text_is_protocol(context: Any) -> None:
    assert isinstance(context.resolved_text, TextBackend)


@then("the container should provide a vector_backend")
def step_then_container_vector(context: Any) -> None:
    context.resolved_vector = context.container.vector_backend()
    assert context.resolved_vector is not None


@then("the vector_backend should be a VectorBackend")
def step_then_vector_is_protocol(context: Any) -> None:
    assert isinstance(context.resolved_vector, VectorBackend)


@then("the container should provide a graph_backend")
def step_then_container_graph(context: Any) -> None:
    context.resolved_graph = context.container.graph_backend()
    assert context.resolved_graph is not None


@then("the graph_backend should be a GraphBackend")
def step_then_graph_is_protocol(context: Any) -> None:
    assert isinstance(context.resolved_graph, GraphBackend)


@then("resolving text_backend twice should return the same instance")
def step_then_text_singleton(context: Any) -> None:
    a = context.container.text_backend()
    b = context.container.text_backend()
    assert a is b


@then("resolving vector_backend twice should return the same instance")
def step_then_vector_singleton(context: Any) -> None:
    a = context.container.vector_backend()
    b = context.container.vector_backend()
    assert a is b


@then("resolving graph_backend twice should return the same instance")
def step_then_graph_singleton(context: Any) -> None:
    a = context.container.graph_backend()
    b = context.container.graph_backend()
    assert a is b
