"""Step definitions for fvcov3 - FAISS vector backend coverage round 3."""

from __future__ import annotations

from typing import Any
from unittest.mock import MagicMock

from behave import given, then, when

from cleveragents.application.services.faiss_vector_backend import (
    FAISSVectorBackend,
    FAISSVectorIndexBackend,
    build_vector_backend,
    build_vector_index_backend,
)
from cleveragents.domain.models.acms.index_stubs import InMemoryVectorIndexBackend
from cleveragents.domain.models.acms.stubs import InMemoryVectorBackend

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _parse_vector(raw: str) -> list[float]:
    return [float(part.strip()) for part in raw.split(",") if part.strip()]


# ---------------------------------------------------------------------------
# Givens
# ---------------------------------------------------------------------------


@given("fvcov3 a mock vector store service")
def step_fvcov3_mock_service(context: Any) -> None:
    mock_svc = MagicMock()
    mock_svc.acms_search_by_vector.return_value = []
    mock_svc.acms_index_embedding.return_value = None
    mock_svc.acms_backend_name.return_value = "faiss"
    context.fvcov3_service = mock_svc
    context.fvcov3_error = None


@given("fvcov3 a FAISSVectorBackend backed by it")
def step_fvcov3_read_backend(context: Any) -> None:
    context.fvcov3_read_backend = FAISSVectorBackend(context.fvcov3_service)


@given("fvcov3 a FAISSVectorIndexBackend backed by it")
def step_fvcov3_write_backend(context: Any) -> None:
    context.fvcov3_write_backend = FAISSVectorIndexBackend(context.fvcov3_service)


@given("fvcov3 a mock vector store service that returns hits without doc_id")
def step_fvcov3_mock_service_no_docid(context: Any) -> None:
    mock_svc = MagicMock()
    mock_svc.acms_search_by_vector.return_value = [
        {
            "content": "some text",
            "score": 0.95,
            "metadata": {"other_key": "val"},  # no doc_id
        },
        {
            "content": "another",
            "score": 0.90,
            "metadata": {"doc_id": ""},  # empty doc_id
        },
    ]
    context.fvcov3_service = mock_svc
    context.fvcov3_error = None


@given("fvcov3 a mock vector store service that returns hits with low scores")
def step_fvcov3_mock_service_low_scores(context: Any) -> None:
    mock_svc = MagicMock()
    mock_svc.acms_search_by_vector.return_value = [
        {
            "content": "low score hit",
            "score": 0.2,
            "metadata": {"doc_id": "doc1"},
        },
        {
            "content": "another low",
            "score": 0.3,
            "metadata": {"doc_id": "doc2"},
        },
    ]
    context.fvcov3_service = mock_svc
    context.fvcov3_error = None


@given("fvcov3 a mock vector store service that returns search hits without doc_id")
def step_fvcov3_mock_service_search_no_docid(context: Any) -> None:
    mock_svc = MagicMock()
    mock_svc.acms_search_by_vector.return_value = [
        {
            "content": "hit text",
            "score": 0.95,
            "metadata": {"some_key": "val"},  # no doc_id
        },
        {
            "content": "hit2",
            "score": 0.90,
            "metadata": {"doc_id": ""},  # empty doc_id
        },
    ]
    context.fvcov3_service = mock_svc
    context.fvcov3_error = None


@given('fvcov3 a mock vector store service with backend name "{name}"')
def step_fvcov3_mock_service_backend_name(context: Any, name: str) -> None:
    mock_svc = MagicMock()
    mock_svc.acms_backend_name.return_value = name
    context.fvcov3_service = mock_svc
    context.fvcov3_error = None


# ---------------------------------------------------------------------------
# Whens
# ---------------------------------------------------------------------------


@when(
    'fvcov3 I index embedding with project "{project}" and doc_id "{doc_id}" and vector "{vector}"'
)
def step_fvcov3_index_embedding(
    context: Any, project: str, doc_id: str, vector: str
) -> None:
    try:
        context.fvcov3_write_backend.index_embedding(
            project,
            doc_id,
            _parse_vector(vector),
            {"location": "src/foo.py"},
        )
        context.fvcov3_error = None
    except (ValueError, Exception) as exc:
        context.fvcov3_error = exc


@when(
    'fvcov3 I index embedding with empty project and doc_id "{doc_id}" and vector "{vector}"'
)
def step_fvcov3_index_embedding_empty_project(
    context: Any, doc_id: str, vector: str
) -> None:
    try:
        context.fvcov3_write_backend.index_embedding(
            "",
            doc_id,
            _parse_vector(vector),
            {"location": "src/foo.py"},
        )
        context.fvcov3_error = None
    except (ValueError, Exception) as exc:
        context.fvcov3_error = exc


@when("fvcov3 I similarity search with empty embedding")
def step_fvcov3_sim_search_empty_embedding(context: Any) -> None:
    try:
        context.fvcov3_read_backend.similarity_search(
            [],
            scope=frozenset({"res1"}),
            top_k=5,
        )
        context.fvcov3_error = None
    except ValueError as exc:
        context.fvcov3_error = exc


@when("fvcov3 I similarity search with top_k {top_k:d}")
def step_fvcov3_sim_search_bad_topk(context: Any, top_k: int) -> None:
    try:
        context.fvcov3_read_backend.similarity_search(
            [1.0, 0.0],
            scope=frozenset({"res1"}),
            top_k=top_k,
        )
        context.fvcov3_error = None
    except ValueError as exc:
        context.fvcov3_error = exc


@when("fvcov3 I perform a valid similarity search")
def step_fvcov3_valid_sim_search(context: Any) -> None:
    context.fvcov3_sim_results = context.fvcov3_read_backend.similarity_search(
        [1.0, 0.0],
        scope=frozenset({"res1"}),
        top_k=10,
    )


@when(
    'fvcov3 I index embedding with project "{project}" and doc_id "{doc_id}" and empty embedding'
)
def step_fvcov3_index_empty_embedding(context: Any, project: str, doc_id: str) -> None:
    try:
        context.fvcov3_write_backend.index_embedding(
            project,
            doc_id,
            [],
            {"location": "src/foo.py"},
        )
        context.fvcov3_error = None
    except ValueError as exc:
        context.fvcov3_error = exc


@when(
    'fvcov3 I index embedding with metadata having resource_type "{rtype}" but no location'
)
def step_fvcov3_index_with_resource_type(context: Any, rtype: str) -> None:
    context.fvcov3_write_backend.index_embedding(
        "proj",
        "doc1",
        [1.0, 0.0],
        {"resource_type": rtype},
    )
    # Capture the content= kwarg passed to acms_index_embedding
    call_args = context.fvcov3_service.acms_index_embedding.call_args
    context.fvcov3_indexed_content = call_args.kwargs.get("content") or call_args[
        1
    ].get("content")


@when('fvcov3 I index embedding with empty metadata for doc_id "{doc_id}"')
def step_fvcov3_index_empty_metadata(context: Any, doc_id: str) -> None:
    context.fvcov3_write_backend.index_embedding(
        "proj",
        doc_id,
        [1.0, 0.0],
        {},
    )
    call_args = context.fvcov3_service.acms_index_embedding.call_args
    context.fvcov3_indexed_content = call_args.kwargs.get("content") or call_args[
        1
    ].get("content")


@when('fvcov3 I search similar with empty query embedding in project "{project}"')
def step_fvcov3_search_similar_empty(context: Any, project: str) -> None:
    try:
        context.fvcov3_write_backend.search_similar(
            project,
            [],
            limit=5,
        )
        context.fvcov3_error = None
    except ValueError as exc:
        context.fvcov3_error = exc


@when('fvcov3 I search similar with limit {limit:d} in project "{project}"')
def step_fvcov3_search_similar_bad_limit(
    context: Any, limit: int, project: str
) -> None:
    try:
        context.fvcov3_write_backend.search_similar(
            project,
            [1.0, 0.0],
            limit=limit,
        )
        context.fvcov3_error = None
    except ValueError as exc:
        context.fvcov3_error = exc


@when('fvcov3 I search similar with min_relevance {min_rel} in project "{project}"')
def step_fvcov3_search_similar_bad_min_relevance(
    context: Any, min_rel: str, project: str
) -> None:
    min_relevance = float(min_rel)
    try:
        results = context.fvcov3_write_backend.search_similar(
            project,
            [1.0, 0.0],
            limit=5,
            min_relevance=min_relevance,
        )
        context.fvcov3_search_results = results
        context.fvcov3_error = None
    except ValueError as exc:
        context.fvcov3_error = exc


@when("fvcov3 I call build_vector_backend")
def step_fvcov3_call_build_vector_backend(context: Any) -> None:
    context.fvcov3_build_result = build_vector_backend(context.fvcov3_service)


@when("fvcov3 I call build_vector_index_backend")
def step_fvcov3_call_build_vector_index_backend(context: Any) -> None:
    context.fvcov3_build_result = build_vector_index_backend(context.fvcov3_service)


# ---------------------------------------------------------------------------
# Thens
# ---------------------------------------------------------------------------


@then('fvcov3 the error message should contain "{fragment}"')
def step_fvcov3_assert_error_message(context: Any, fragment: str) -> None:
    assert context.fvcov3_error is not None, "Expected an error but none was raised"
    assert fragment in str(context.fvcov3_error), (
        f"Expected '{fragment}' in error message, got: {context.fvcov3_error}"
    )


@then("fvcov3 the similarity search results should be empty")
def step_fvcov3_assert_sim_results_empty(context: Any) -> None:
    assert context.fvcov3_sim_results == [], (
        f"Expected empty results, got {context.fvcov3_sim_results}"
    )


@then('fvcov3 the indexed content should be "{expected}"')
def step_fvcov3_assert_indexed_content(context: Any, expected: str) -> None:
    assert context.fvcov3_indexed_content == expected, (
        f"Expected content '{expected}', got '{context.fvcov3_indexed_content}'"
    )


@then("fvcov3 the search similar results should be empty")
def step_fvcov3_assert_search_similar_empty(context: Any) -> None:
    assert context.fvcov3_search_results == [], (
        f"Expected empty results, got {context.fvcov3_search_results}"
    )


@then("fvcov3 the result should be an InMemoryVectorBackend")
def step_fvcov3_assert_inmemory_vector(context: Any) -> None:
    assert isinstance(context.fvcov3_build_result, InMemoryVectorBackend), (
        f"Expected InMemoryVectorBackend, got {type(context.fvcov3_build_result)}"
    )


@then("fvcov3 the result should be an InMemoryVectorIndexBackend")
def step_fvcov3_assert_inmemory_index(context: Any) -> None:
    assert isinstance(context.fvcov3_build_result, InMemoryVectorIndexBackend), (
        f"Expected InMemoryVectorIndexBackend, got {type(context.fvcov3_build_result)}"
    )
