"""Implement HyDE (Hypothetical Document Embeddings) query expansion.

HyDE improves dense retrieval by addressing the vocabulary gap between
short user queries and longer document passages:

  User query  →  [LLM generates hypothetical answer]
                         ↓
             embed hypothetical answer (not original query)
                         ↓
             retrieve similar real passages from Milvus

The hypothetical answer uses the same vocabulary and phrasing as documents,
so its embedding is much closer to relevant chunks than a terse query embedding.

Usage:
    expander = HyDEExpander()
    retrieval_query = expander.expand(query, provider=..., model=...)
    chunks = retrieval_service.retrieve(query=retrieval_query, ...)

When the LLM call fails, expand() falls back to the original query so the
retrieval pipeline degrades gracefully.

References:
    Gao et al. (2022), "Precise Zero-Shot Dense Retrieval without Relevance Labels"
    https://arxiv.org/abs/2212.10496
"""

from __future__ import annotations

from loguru import logger

from app.config.settings import settings
from app.services.llm.llm_factory import get_llm_client

# Maximum chars to trim from the hypothetical answer to avoid token overrun.
_MAX_HYPOTHESIS_CHARS = 600

# System prompt that instructs the LLM to write a passage *as if* it were
# from a regulatory document, not a conversation answer.
_HYDE_SYSTEM = (
    "你是一位法规知识库专家。用户提出了一个问题，"
    "请用50-120字写一段话，模拟如果相关法规文档中存在完美答案，"
    "该段落会是什么内容。\n\n"
    "要求：\n"
    "- 使用与法规文档相同的正式书面语气\n"
    "- 包含可能的条款编号、标准名称等关键术语\n"
    "- 不要解释你在做什么，直接输出假设性段落\n"
    "- 如问题过于模糊，写一段合理的通用法规说明"
)


class HyDEExpander:
    """Generate a hypothetical document passage to improve dense retrieval.

    The expander is stateless — instantiate once and call expand() per query.
    It requires no external dependencies beyond the project's existing LLM
    client infrastructure.
    """

    def expand(self, query: str) -> str:
        """Return a combined retrieval query: original query + hypothetical passage.

        The combination ensures:
        - Dense retrieval uses the enriched hypothetical text (semantic match).
        - BM25 retrieval still benefits from the original query keywords.

        The model used is ``settings.hyde_llm_model`` (dedicated lightweight model)
        falling back to the main ``settings.llm_model`` when not configured.

        If the LLM call fails for any reason, returns the original query unchanged.
        """
        if not settings.hyde_enabled:
            return query

        # Use the dedicated HyDE model when configured; fall back to main LLM.
        # A lightweight model (e.g. qwen3.5-flash) is sufficient for generating
        # a short hypothetical passage and significantly reduces cost + latency.
        provider = settings.hyde_llm_provider or settings.llm_provider
        model = settings.hyde_llm_model or settings.llm_model

        try:
            client = get_llm_client(provider=provider, model=model)
            resp = client.chat(
                messages=[
                    {"role": "system", "content": _HYDE_SYSTEM},
                    {"role": "user", "content": f"问题：{query}"},
                ],
                max_tokens=settings.hyde_max_tokens,
                # Low temperature: we want a plausible, deterministic passage.
                temperature=0.3,
            )
            if not resp.is_success or not resp.content:
                logger.debug("HyDE LLM call failed or empty — using original query")
                return query

            hypothesis = resp.content.strip()[:_MAX_HYPOTHESIS_CHARS]
            logger.debug("HyDE expanded query ({}→{} chars)", len(query), len(hypothesis))

            # Concatenate: the embedding model will see the full combined text,
            # so the resulting vector leans toward the hypothetical document style.
            return f"{query}\n\n{hypothesis}"

        except Exception as exc:  # noqa: BLE001 — intentional broad catch for graceful fallback
            logger.warning("HyDE expansion failed: {} — using original query", exc)
            return query
