from json import dumps

from requests import HTTPError, post
from yarl import URL

from dify_plugin.entities import I18nObject
from dify_plugin.entities.model import AIModelEntity, FetchFrom, ModelType
from dify_plugin.entities.model.rerank import RerankDocument, RerankResult
from dify_plugin.errors.model import (
    CredentialsValidateFailedError,
    InvokeError,
    InvokeServerUnavailableError,
)
from dify_plugin.interfaces.model.rerank_model import RerankModel


class OAICompatRerankModel(RerankModel):
    """
    rerank model API is compatible with Jina rerank model API. So copy the JinaRerankModel class code here.
    we need enhance for llama.cpp , which return raw score, not normalize score 0~1.  It seems Dify need it
    """

    def _invoke(
        self,
        model: str,
        credentials: dict,
        query: str,
        docs: list[str],
        score_threshold: float | None = None,
        top_n: int | None = None,
        user: str | None = None,
    ) -> RerankResult:
        """
        Invoke rerank model

        :param model: model name
        :param credentials: model credentials
        :param query: search query
        :param docs: docs for reranking
        :param score_threshold: score threshold
        :param top_n: top n documents to return
        :param user: unique user id
        :return: rerank result
        """
        if len(docs) == 0:
            return RerankResult(model=model, docs=[])

        server_url = credentials["endpoint_url"]
        model_name = model

        if not server_url:
            raise CredentialsValidateFailedError("server_url is required")
        if not model_name:
            raise CredentialsValidateFailedError("model_name is required")

        url = server_url
        headers = {
            "Authorization": f"Bearer {credentials.get('api_key')}",
            "Content-Type": "application/json",
        }

        # TODO: Do we need truncate docs to avoid llama.cpp return error?

        data = {
            "model": credentials.get("endpoint_model_name", model),
            "query": query,
            "texts": docs,
            "top_n": top_n,

        }
        try:
            print("重排序模型接口是 {}".format(URL(url) / "rerank"))
            print("重排序模型--发送的数据是 {}-- {}".format(data,headers))
            response = post(str(URL(url) / "rerank"), headers=headers, data=dumps(data),timeout=60)
            response.raise_for_status()
            results = {'results':response.json()}
            print("重排序模型接口返回的数据是 {}".format(results))
            rerank_documents = []
            scores = [result["score"] for result in results["results"]]

            # Min-Max Normalization: Normalize scores to 0 ~ 1.0 range
            min_score = min(scores)
            max_score = max(scores)
            score_range = max_score - min_score if max_score != min_score else 1.0  # Avoid division by zero
            print("重排序模型接口--计算得分是 {}".format(score_range))

            for result in results["results"]:
                index = result["index"]

                # Retrieve document text (fallback if llama.cpp rerank doesn't return it)
                text = docs[index]
                # document = result.get("document", {})
                document = text
                if document:
                    print("重排序模型接口-找到document \n{}".format(document))
                    if isinstance(document, dict):
                        text = document.get("text", docs[index])
                    elif isinstance(document, str):
                        text = document

                # Normalize the score
                normalized_score = (result["score"] - min_score) / score_range
                print("重排序模型接口---normalized_score \n{}".format(normalized_score))
                # Create RerankDocument object with normalized score
                rerank_document = RerankDocument(
                    index=index,
                    text=text,
                    score=normalized_score,
                )

                # Apply threshold (if defined)
                if score_threshold is None or normalized_score >= score_threshold:
                    rerank_documents.append(rerank_document)

            # Sort rerank_documents by normalized score in descending order
            rerank_documents.sort(key=lambda doc: doc.score, reverse=True)

            return RerankResult(model=model, docs=rerank_documents)

        except HTTPError as e:
            raise InvokeServerUnavailableError(str(e)) from e

    def validate_credentials(self, model: str, credentials: dict) -> None:
        """
        Validate model credentials

        :param model: model name
        :param credentials: model credentials
        :return:
        """
        try:
            self._invoke(
                model=model,
                credentials=credentials,
                query="What is the capital of the United States?",
                docs=[
                    "Carson City is the capital city of the American state of Nevada. At the 2010 United States "
                    "Census, Carson City had a population of 55,274.",
                    "The Commonwealth of the Northern Mariana Islands is a group of islands in the Pacific Ocean that "
                    "are a political division controlled by the United States. Its capital is Saipan.",
                ],
                score_threshold=0.8,
            )
        except Exception as ex:
            raise CredentialsValidateFailedError(str(ex)) from ex

    def get_customizable_model_schema(self, model: str, credentials: dict) -> AIModelEntity:
        """
        generate custom model entities from credentials
        """
        entity = AIModelEntity(
            model=model,
            label=I18nObject(en_US=model),
            model_type=ModelType.RERANK,
            fetch_from=FetchFrom.CUSTOMIZABLE_MODEL,
            model_properties={},
        )

        return entity

    @property
    def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]:
        """
        Map model invoke error to unified error
        """
        return {}
