# Copyright (c) Microsoft. All rights reserved.

import sys
from typing import Any

from pydantic import PrivateAttr

from semantic_kernel.connectors.ai.embedding_generator_base import EmbeddingGeneratorBase
from semantic_kernel.memory.memory_query_result import MemoryQueryResult
from semantic_kernel.memory.memory_record import MemoryRecord
from semantic_kernel.memory.memory_store_base import MemoryStoreBase
from semantic_kernel.memory.semantic_text_memory_base import SemanticTextMemoryBase

if sys.version_info >= (3, 13):
    from warnings import deprecated
else:
    from typing_extensions import deprecated


@deprecated("This class will be removed in a future version.")
class SemanticTextMemory(SemanticTextMemoryBase):
    """Class for semantic text memory."""

    _storage: MemoryStoreBase = PrivateAttr()
    _embeddings_generator: EmbeddingGeneratorBase = PrivateAttr()

    def __init__(self, storage: MemoryStoreBase, embeddings_generator: EmbeddingGeneratorBase) -> None:
        """Initialize a new instance of SemanticTextMemory.

        Args:
            storage (MemoryStoreBase): The MemoryStoreBase to use for storage.
            embeddings_generator (EmbeddingGeneratorBase): The EmbeddingGeneratorBase
                to use for generating embeddings.
        """
        super().__init__()
        self._storage = storage
        self._embeddings_generator = embeddings_generator

    async def save_information(
        self,
        collection: str,
        text: str,
        id: str,
        description: str | None = None,
        additional_metadata: str | None = None,
        embeddings_kwargs: dict[str, Any] | None = None,
    ) -> None:
        """Save information to the memory (calls the memory store's upsert method).

        Args:
            collection (str): The collection to save the information to.
            text (str): The text to save.
            id (str): The id of the information.
            description (Optional[str]): The description of the information.
            additional_metadata (Optional[str]): Additional metadata of the information.
            embeddings_kwargs (Optional[Dict[str, Any]]): The embeddings kwargs of the information.
        """
        if not await self._storage.does_collection_exist(collection_name=collection):
            await self._storage.create_collection(collection_name=collection)

        embedding = (await self._embeddings_generator.generate_embeddings([text], **(embeddings_kwargs or {})))[0]
        data = MemoryRecord.local_record(
            id=id,
            text=text,
            description=description,
            additional_metadata=additional_metadata,
            embedding=embedding,
        )

        await self._storage.upsert(collection_name=collection, record=data)

    async def save_reference(
        self,
        collection: str,
        text: str,
        external_id: str,
        external_source_name: str,
        description: str | None = None,
        additional_metadata: str | None = None,
        embeddings_kwargs: dict[str, Any] | None = None,
    ) -> None:
        """Save a reference to the memory (calls the memory store's upsert method).

        Args:
            collection (str): The collection to save the reference to.
            text (str): The text to save.
            external_id (str): The external id of the reference.
            external_source_name (str): The external source name of the reference.
            description (Optional[str]): The description of the reference.
            additional_metadata (Optional[str]): Additional metadata of the reference.
            embeddings_kwargs (Optional[Dict[str, Any]]): The embeddings kwargs of the reference.
        """
        if not await self._storage.does_collection_exist(collection_name=collection):
            await self._storage.create_collection(collection_name=collection)

        embedding = (await self._embeddings_generator.generate_embeddings([text], **(embeddings_kwargs or {})))[0]
        data = MemoryRecord.reference_record(
            external_id=external_id,
            source_name=external_source_name,
            description=description,
            additional_metadata=additional_metadata,
            embedding=embedding,
        )

        await self._storage.upsert(collection_name=collection, record=data)

    async def get(
        self,
        collection: str,
        key: str,
    ) -> MemoryQueryResult | None:
        """Get information from the memory (calls the memory store's get method).

        Args:
            collection (str): The collection to get the information from.
            key (str): The key of the information.

        Returns:
            Optional[MemoryQueryResult]: The MemoryQueryResult if found, None otherwise.
        """
        record = await self._storage.get(collection_name=collection, key=key)
        return MemoryQueryResult.from_memory_record(record, 1.0) if record else None

    async def search(
        self,
        collection: str,
        query: str,
        limit: int = 1,
        min_relevance_score: float = 0.0,
        with_embeddings: bool = False,
        embeddings_kwargs: dict[str, Any] | None = None,
    ) -> list[MemoryQueryResult]:
        """Search the memory (calls the memory store's get_nearest_matches method).

        Args:
            collection (str): The collection to search in.
            query (str): The query to search for.
            limit (int): The maximum number of results to return. (default: {1})
            min_relevance_score (float): The minimum relevance score to return. (default: {0.0})
            with_embeddings (bool): Whether to return the embeddings of the results. (default: {False})
            embeddings_kwargs (Optional[Dict[str, Any]]): The embeddings kwargs of the information.

        Returns:
            List[MemoryQueryResult]: The list of MemoryQueryResult found.
        """
        query_embedding = (await self._embeddings_generator.generate_embeddings([query], **(embeddings_kwargs or {})))[
            0
        ]
        results = await self._storage.get_nearest_matches(
            collection_name=collection,
            embedding=query_embedding,
            limit=limit,
            min_relevance_score=min_relevance_score,
            with_embeddings=with_embeddings,
        )

        return [MemoryQueryResult.from_memory_record(r[0], r[1]) for r in results]

    async def get_collections(self) -> list[str]:
        """Get the list of collections in the memory (calls the memory store's get_collections method).

        Returns:
            List[str]: The list of all the memory collection names.
        """
        return await self._storage.get_collections()
