"""FAISS-backed ACMS vector backend adapters.

Bridges the ACMS read/write vector backend protocols to the shared
``VectorStoreService`` FAISS persistence helpers.
"""

from __future__ import annotations

from typing import Any

import structlog

from cleveragents.application.services.vector_store_service import (
    FAISS,
    VectorStoreService,
)
from cleveragents.domain.models.acms.backends import VectorResult
from cleveragents.domain.models.acms.index_backends import SearchResult
from cleveragents.domain.models.acms.index_stubs import InMemoryVectorIndexBackend
from cleveragents.domain.models.acms.stubs import InMemoryVectorBackend

logger = structlog.get_logger(__name__)


def _require_non_empty(value: str, name: str) -> str:
    trimmed = value.strip()
    if not trimmed:
        raise ValueError(f"{name} must be a non-empty string")
    return trimmed


class FAISSVectorBackend:
    """Read-side ACMS vector backend backed by the shared FAISS store."""

    def __init__(self, vector_store_service: VectorStoreService) -> None:
        self._vector_store_service = vector_store_service

    def similarity_search(
        self,
        embedding: list[float],
        *,
        scope: frozenset[str],
        top_k: int = 20,
    ) -> list[VectorResult]:
        """Search the shared ACMS FAISS store."""

        if not embedding:
            raise ValueError("embedding must be a non-empty list")
        if top_k < 1:
            raise ValueError(f"top_k must be positive, got {top_k}")

        hits = self._vector_store_service.acms_search_by_vector(
            list(embedding),
            top_k=top_k,
            scope=scope,
        )
        results: list[VectorResult] = []
        for hit in hits:
            metadata = dict(hit["metadata"])
            doc_id = metadata.pop("doc_id", "")
            if not doc_id:
                continue
            results.append(
                VectorResult(
                    uko_uri=doc_id,
                    content=str(hit["content"]),
                    score=float(hit["score"]),
                    metadata=metadata,
                )
            )
        return results


class FAISSVectorIndexBackend:
    """Write-side ACMS vector index backend backed by the shared FAISS store."""

    def __init__(self, vector_store_service: VectorStoreService) -> None:
        self._vector_store_service = vector_store_service

    def index_embedding(
        self,
        project: str,
        doc_id: str,
        embedding: list[float],
        metadata: dict[str, str],
    ) -> None:
        """Index an embedding in the shared ACMS FAISS store."""

        normalized_project = _require_non_empty(project, "project")
        normalized_doc_id = _require_non_empty(doc_id, "doc_id")
        if not embedding:
            raise ValueError("embedding must be a non-empty list")

        payload = metadata.get("location") or metadata.get(
            "resource_type", f"embedding:{normalized_doc_id}"
        )
        self._vector_store_service.acms_index_embedding(
            normalized_project,
            normalized_doc_id,
            list(embedding),
            dict(metadata),
            content=payload,
        )

    def search_similar(
        self,
        project: str,
        query_embedding: list[float],
        limit: int = 20,
        min_relevance: float = 0.0,
    ) -> list[SearchResult]:
        """Search embeddings within one project."""

        normalized_project = _require_non_empty(project, "project")
        if not query_embedding:
            raise ValueError("query_embedding must be a non-empty list")
        if limit < 1:
            raise ValueError(f"limit must be positive, got {limit}")
        if not (0.0 <= min_relevance <= 1.0):
            raise ValueError(
                f"min_relevance must be in [0.0, 1.0], got {min_relevance}"
            )

        hits = self._vector_store_service.acms_search_by_vector(
            list(query_embedding),
            top_k=limit,
            project=normalized_project,
        )
        results: list[SearchResult] = []
        for hit in hits:
            score = float(hit["score"])
            if score < min_relevance:
                continue
            metadata = dict(hit["metadata"])
            doc_id = metadata.pop("doc_id", "")
            if not doc_id:
                continue
            results.append(
                SearchResult(
                    doc_id=doc_id,
                    content=str(hit["content"]),
                    score=score,
                    metadata=metadata,
                )
            )
        return results

    def remove_embedding(self, project: str, doc_id: str) -> None:
        """Remove one embedding from the shared ACMS FAISS store."""

        normalized_project = _require_non_empty(project, "project")
        normalized_doc_id = _require_non_empty(doc_id, "doc_id")
        self._vector_store_service.acms_remove_embedding(
            normalized_project,
            normalized_doc_id,
        )


def build_vector_backend(vector_store_service: VectorStoreService) -> Any:
    """Return the configured ACMS read-side vector backend."""

    backend_name = vector_store_service.acms_backend_name()
    if backend_name != "faiss":
        logger.info(
            "acms.vector_backend.fallback",
            configured_backend=backend_name,
            fallback_backend="InMemoryVectorBackend",
        )
        return InMemoryVectorBackend()
    if FAISS is None:
        logger.warning(
            "acms.vector_backend.faiss_unavailable",
            fallback_backend="InMemoryVectorBackend",
        )
        return InMemoryVectorBackend()
    return FAISSVectorBackend(vector_store_service)


def build_vector_index_backend(vector_store_service: VectorStoreService) -> Any:
    """Return the configured ACMS write-side vector backend."""

    backend_name = vector_store_service.acms_backend_name()
    if backend_name != "faiss":
        logger.info(
            "acms.vector_index_backend.fallback",
            configured_backend=backend_name,
            fallback_backend="InMemoryVectorIndexBackend",
        )
        return InMemoryVectorIndexBackend()
    if FAISS is None:
        logger.warning(
            "acms.vector_index_backend.faiss_unavailable",
            fallback_backend="InMemoryVectorIndexBackend",
        )
        return InMemoryVectorIndexBackend()
    return FAISSVectorIndexBackend(vector_store_service)


__all__: list[str] = [
    "FAISSVectorBackend",
    "FAISSVectorIndexBackend",
    "build_vector_backend",
    "build_vector_index_backend",
]
