import json
from typing import Any, Optional
from uuid import UUID

from shared.api.models import (
    WrappedBooleanResponse,
    WrappedChunkResponse,
    WrappedChunksResponse,
    WrappedVectorSearchResponse,
)

from ..models import SearchSettings


class ChunksSDK:
    """SDK for interacting with chunks in the v3 API."""

    def __init__(self, client):
        self.client = client

    async def update(
        self,
        chunk: dict[str, str],
    ) -> WrappedChunkResponse:
        """Update an existing chunk.

        Args:
            chunk (dict[str, str]): Chunk to update. Should contain:
                - id: UUID of the chunk
                - metadata: Dictionary of metadata
        Returns:
            WrappedChunkResponse
        """
        response_dict = await self.client._make_request(
            "POST",
            f"chunks/{str(chunk['id'])}",
            json=chunk,
            version="v3",
        )

        return WrappedChunkResponse(**response_dict)

    async def retrieve(
        self,
        id: str | UUID,
    ) -> WrappedChunkResponse:
        """Get a specific chunk.

        Args:
            id (str | UUID): Chunk ID to retrieve

        Returns:
            WrappedChunkResponse
        """

        response_dict = await self.client._make_request(
            "GET",
            f"chunks/{id}",
            version="v3",
        )

        return WrappedChunkResponse(**response_dict)

    # FIXME: Is this the most appropriate name for this method?
    async def list_by_document(
        self,
        document_id: str | UUID,
        metadata_filter: Optional[dict] = None,
        offset: Optional[int] = 0,
        limit: Optional[int] = 100,
    ) -> WrappedChunksResponse:
        """List chunks for a specific document.

        Args:
            document_id (str | UUID): Document ID to get chunks for
            metadata_filter (Optional[dict]): Filter chunks by metadata
            offset (int, optional): Specifies the number of objects to skip. Defaults to 0.
            limit (int, optional): Specifies a limit on the number of objects to return, ranging between 1 and 100. Defaults to 100.

        Returns:
            WrappedChunksResponse
        """
        params: dict = {
            "offset": offset,
            "limit": limit,
        }
        if metadata_filter:
            params["metadata_filter"] = json.dumps(metadata_filter)

        response_dict = await self.client._make_request(
            "GET",
            f"documents/{str(document_id)}/chunks",
            params=params,
            version="v3",
        )

        return WrappedChunksResponse(**response_dict)

    async def delete(
        self,
        id: str | UUID,
    ) -> WrappedBooleanResponse:
        """Delete a specific chunk.

        Args:
            id (str | UUID): ID of chunk to delete

        Returns:
            WrappedBooleanResponse
        """
        response_dict = await self.client._make_request(
            "DELETE",
            f"chunks/{str(id)}",
            version="v3",
        )

        return WrappedBooleanResponse(**response_dict)

    async def list(
        self,
        include_vectors: bool = False,
        metadata_filter: Optional[dict] = None,
        offset: Optional[int] = 0,
        limit: Optional[int] = 100,
        filters: Optional[dict] = None,
    ) -> WrappedChunksResponse:
        """List chunks with pagination support.

        Args:
            include_vectors (bool, optional): Include vector data in response. Defaults to False.
            metadata_filter (Optional[dict], optional): Filter by metadata. Defaults to None.
            offset (int, optional): Specifies the number of objects to skip. Defaults to 0.
            limit (int, optional): Specifies a limit on the number of objects to return, ranging between 1 and 100. Defaults to 100.

        Returns:
            WrappedChunksResponse
        """
        params: dict = {
            "offset": offset,
            "limit": limit,
            "include_vectors": include_vectors,
        }
        if filters:
            params["filters"] = json.dumps(filters)

        if metadata_filter:
            params["metadata_filter"] = json.dumps(metadata_filter)

        response_dict = await self.client._make_request(
            "GET",
            "chunks",
            params=params,
            version="v3",
        )

        return WrappedChunksResponse(**response_dict)

    async def search(
        self,
        query: str,
        search_settings: Optional[dict | SearchSettings] = None,
    ) -> WrappedVectorSearchResponse:
        """Conduct a vector and/or graph search.

        Args:
            query (str): The query to search for.
            search_settings (Optional[dict, SearchSettings]]): Vector search settings.

        Returns:
            WrappedVectorSearchResponse
        """
        if search_settings and not isinstance(search_settings, dict):
            search_settings = search_settings.model_dump()

        data: dict[str, Any] = {
            "query": query,
            "search_settings": search_settings,
        }
        response_dict = await self.client._make_request(
            "POST",
            "chunks/search",
            json=data,
            version="v3",
        )

        return WrappedVectorSearchResponse(**response_dict)
