from django.utils.encoding import force_str
from elasticsearch_dsl import Q
from haystack.backends import BaseEngine, BaseSearchBackend, BaseSearchQuery, log_query
from haystack.forms import ModelSearchForm
from haystack.models import SearchResult
from haystack.utils import log as logging

from blog.documents import ArticleDocument, ArticleDocumentManager
from blog.models import Article

logger = logging.getLogger(__name__)

class ElasticSearchBackend(BaseSearchBackend):
    """
    sh:
    自定义Elasticsearch搜索后端，继承Haystack的BaseSearchBackend
    实现与Elasticsearch的交互逻辑（数据同步、搜索查询、拼写建议等）
    """
    def __init__(self, connection_alias, **connection_options):
        super(
            ElasticSearchBackend,
            self).__init__(
            connection_alias,
            **connection_options)
        self.manager = ArticleDocumentManager()
        self.include_spelling = True

    def _get_models(self, iterable):
        """
        将模型实例列表转换为Elasticsearch文档对象
        iterable：待转换的模型实例集合（可为空）
        """
        models = iterable if iterable and iterable[0] else Article.objects.all()
        docs = self.manager.convert_to_doc(models)
        return docs

    def _create(self, models):
        """创建Elasticsearch索引并批量写入文档"""
        self.manager.create_index()
        docs = self._get_models(models)
        self.manager.rebuild(docs)

    def _delete(self, models):
        """从Elasticsearch中删除指定模型对应的文档"""
        for m in models:
            m.delete()
        return True

    def _rebuild(self, models):
        """更新Elasticsearch索引（增量同步文档）"""
        models = models if models else Article.objects.all()
        docs = self.manager.convert_to_doc(models)
        self.manager.update_docs(docs)

    def update(self, index, iterable, commit=True):
        """Haystack规范方法：更新搜索索引（接收Haystack的索引更新请求）"""
        models = self._get_models(iterable)
        self.manager.update_docs(models)

    def remove(self, obj_or_string):
        """Haystack规范方法：移除索引中指定对象"""
        models = self._get_models([obj_or_string])
        self._delete(models)

    def clear(self, models=None, commit=True):
        """Haystack规范方法：清空索引"""
        self.remove(None)

    @staticmethod
    def get_suggestion(query: str) -> str:
        """获取推荐词, 如果没有找到添加原搜索词"""
        search = ArticleDocument.search() \
            .query("match", body=query) \
            .suggest('suggest_search', query, term={'field': 'body'}) \
            .execute()

        keywords = []
        for suggest in search.suggest.suggest_search:
            if suggest["options"]:
                keywords.append(suggest["options"][0]["text"])
            else:
                keywords.append(suggest["text"])

        return ' '.join(keywords)

    @log_query
    def search(self, query_string, **kwargs):
        """
        核心搜索方法：执行Elasticsearch查询并封装结果
        query_string：用户输入的搜索关键词
        kwargs：附加参数（分页偏移量等）
        """
        logger.info('search query_string:' + query_string)
        start_offset = kwargs.get('start_offset')
        end_offset = kwargs.get('end_offset')

        # 推荐词搜索
        if getattr(self, "is_suggest", None):
            suggestion = self.get_suggestion(query_string)
        else:
            suggestion = query_string

        q = Q('bool',
              should=[Q('match', body=suggestion), Q('match', title=suggestion)],
              minimum_should_match="70%")

        search = ArticleDocument.search() \
                     .query('bool', filter=[q]) \
                     .filter('term', status='p') \
                     .filter('term', type='a') \
                     .source(False)[start_offset: end_offset]

        results = search.execute()
        hits = results['hits'].total
        raw_results = []
        for raw_result in results['hits']['hits']:
            app_label = 'blog'
            model_name = 'Article'
            additional_fields = {}

            result_class = SearchResult

            result = result_class(
                app_label,
                model_name,
                raw_result['_id'],
                raw_result['_score'],
                **additional_fields)
            raw_results.append(result)
        facets = {}
        spelling_suggestion = None if query_string == suggestion else suggestion

        return {
            'results': raw_results,
            'hits': hits,
            'facets': facets,
            'spelling_suggestion': spelling_suggestion,
        }


class ElasticSearchQuery(BaseSearchQuery):
    """
    自定义搜索查询类，继承Haystack的BaseSearchQuery
    处理查询参数清洗、格式转换、结果计数等逻辑
    """
    def _convert_datetime(self, date):
        if hasattr(date, 'hour'):
            return force_str(date.strftime('%Y%m%d%H%M%S'))
        else:
            return force_str(date.strftime('%Y%m%d000000'))

    def clean(self, query_fragment):
        """
        清洗用户输入的搜索关键词
        处理保留词、特殊字符转义，避免查询语法错误
        """
        words = query_fragment.split()
        cleaned_words = []

        for word in words:
            if word in self.backend.RESERVED_WORDS:
                word = word.replace(word, word.lower())

            for char in self.backend.RESERVED_CHARACTERS:
                if char in word:
                    word = "'%s'" % word
                    break

            cleaned_words.append(word)

        return ' '.join(cleaned_words)

    def build_query_fragment(self, field, filter_type, value):
        """构建查询片段（适配Haystack查询构建逻辑）"""
        return value.query_string

    def get_count(self):
        """获取搜索结果总数"""
        results = self.get_results()
        return len(results) if results else 0

    def get_spelling_suggestion(self, preferred_query=None):
        """获取拼写建议（对接后端的关键词推荐功能）"""
        return self._spelling_suggestion

    def build_params(self, spelling_query=None):
        """构建查询参数（继承父类逻辑，可自定义扩展）"""
        kwargs = super(ElasticSearchQuery, self).build_params(spelling_query=spelling_query)
        return kwargs


class ElasticSearchModelSearchForm(ModelSearchForm):
    """
    自定义搜索表单，继承Haystack的ModelSearchForm
    控制是否启用关键词推荐功能
    """
    def search(self):
        self.searchqueryset.query.backend.is_suggest = self.data.get("is_suggest") != "no"
        sqs = super().search()
        return sqs


class ElasticSearchEngine(BaseEngine):
    """
    Haystack搜索引擎入口类，关联自定义的Backend和Query
    供Django项目配置使用（在settings中指定该引擎）
    """
    backend = ElasticSearchBackend
    query = ElasticSearchQuery
