from typing import Optional, List

from clarabot_nano.common.solr.reflect import SolrSchemaHighlightReflector
from clarabot_nano.common.solr.rest import build_query_dismax_args, quote_escape
from clarabot_nano.server.general.solr.input_processing import INPUT_PROCESSORS, UNIVERSAL_INPUT_PROCESSOR


class InvalidQueryError(Exception):
    pass


class SolrSchemaQueryReflector(SolrSchemaHighlightReflector):
    """
    Prepare common query arguments from the reflected schema.
    """

    def __init__(self):
        SolrSchemaHighlightReflector.__init__(self)
        self.query_fields_by_language = dict()
        self.highlight_fields = ""

    def _finalize_reflect(self, context):
        SolrSchemaHighlightReflector._finalize_reflect(self, context)
        # # build query args
        query_fields_lang_map = dict(
            (lang, self.create_search_query_fields(lang, self.languages)) for lang in self.languages
        )
        # query_fields_fallback = self._create_search_query_fields(self.FALLBACK_FIELD, self.schema_languages)
        # so api caller can decide to not have language bias
        query_fields_lang_map[""] = self.create_search_query_fields("", self.languages)
        query_fields_lang_map[None] = query_fields_lang_map[""]
        self.query_fields_by_language = query_fields_lang_map
        self.highlight_fields = " ".join(sorted(self.highlight_field_by_language.values()))

    @classmethod
    def create_search_query_fields(
        cls, lang: str, languages: List[str], lang_bias=2.1, summary_bias=1.3, fallback_value=0.7, exact_bias=1.75
    ) -> str:
        """
        Generate search field value distribution for all languages by the selected language (if any).
        """
        fields = []
        for c_lang in languages:
            if lang == c_lang:
                fields.append(f"summary_{c_lang}_ex^{round(summary_bias * lang_bias * exact_bias, 2)}")
                fields.append(f"text_{c_lang}_ex^{round(lang_bias * exact_bias, 2)}")
                fields.append(f"summary_{c_lang}^{round(summary_bias * lang_bias, 2)}")
                fields.append(f"text_{c_lang}^{lang_bias}")
            else:
                fields.append(f"summary_{c_lang}_ex^{round(summary_bias * exact_bias, 2)}")
                fields.append(f"text_{c_lang}_ex^{exact_bias}")
                fields.append(f"summary_{c_lang}^{summary_bias}")
                fields.append(f"text_{c_lang}")
        if lang == cls.FALLBACK_FIELD:
            fields.append(f"summary_{cls.FALLBACK_FIELD}^{round(summary_bias * lang_bias, 2)}")
            fields.append(f"text_{cls.FALLBACK_FIELD}^{lang_bias}")
        else:
            fields.append(f"summary_{cls.FALLBACK_FIELD}^{round(summary_bias * fallback_value, 2)}")
            fields.append(f"text_{cls.FALLBACK_FIELD}^{fallback_value}")
        return " ".join(fields)

    def create_search_query_args(self, root_path: str, query_exp, lang: Optional[str] = None):
        if not root_path:
            raise InvalidQueryError("Accessor root must not be empty")
        query_exp = INPUT_PROCESSORS.get(lang, UNIVERSAL_INPUT_PROCESSOR).process(query_exp)
        filter_queries = ["file:1", f'accessors:"{quote_escape(root_path)}"']
        # if category:
        #     filter_queries.append('category:%s' % category)
        # if deny_tags:
        #     filter_queries.append(" AND ".join('-tags:"%s"' % tag for tag in deny_tags))
        if not (query_fields := self.query_fields_by_language.get(lang)):
            query_fields = self.query_fields_by_language[None]
        return build_query_dismax_args(
            query_exp,
            query_fields,
            fields="path,accessors,size,mtime,score",
            start=0,
            rows=20,
            sort="score desc",
            filter_queries=filter_queries,
            mm_auto_relax=True,
            highlight=True,
            extra_args=[
                ("hl.fl", self.highlight_fields),
                ("tie", "0.11"),
            ],
        )
