from __future__ import annotations

from typing import Any

from sqlalchemy.orm import Session

from app.core.logging import get_logger
from app.core.secret_box import decrypt_secret
from app.models.financial_record import ExpenseClaim
from app.models.system_model_setting import SystemModelSetting
from app.services.embedding_provider import EmbeddingProvider
from app.services.few_shot_retrieval import FewShotRetriever
from app.services.few_shot_store import FewShotStore
from app.services.knowledge_rag_runtime import RuntimeModelConfig

logger = get_logger("app.services.expense_claim_historical_evidence")

_SCENE_BY_BUSINESS_STAGE = {
    "expense_application": "expense_application",
    "reimbursement": "expense_reimbursement",
}
_LABEL_TEXT = {
    "confirmed": "历史已确认，仅供复核",
    "false_positive": "历史误报，仅供复核",
}
_SUMMARY_BY_LABEL = {
    "confirmed": "历史相似案例经人工复核确认风险成立。",
    "false_positive": "历史相似案例经人工复核判定为误报。",
}
_MAX_RULE_CONTEXTS = 3
_MAX_EVIDENCE = 3


class ExpenseClaimHistoricalEvidenceService:
    """为费用预审补充历史案例证据，但不参与确定性决策。"""

    def __init__(self, db: Session) -> None:
        self.db = db

    def retrieve(
        self,
        claim: ExpenseClaim,
        *,
        tenant_id: str,
        business_stage: str,
        findings: list[dict[str, Any]],
    ) -> list[dict[str, Any]]:
        tenant = _text(tenant_id)
        scene_code = _SCENE_BY_BUSINESS_STAGE.get(_text(business_stage), "")
        if not tenant or not scene_code:
            return []

        try:
            retriever = self._build_retriever()
            if retriever is None:
                return []
            query = self._build_query(claim, findings=findings)
            evidence: list[dict[str, Any]] = []
            emitted_sample_ids: set[str] = set()
            for policy_ref, rule_version in self._rule_contexts(findings):
                hits = retriever.retrieve_for_expense_case(
                    tenant_id=tenant,
                    scene=scene_code,
                    policy_ref=policy_ref,
                    rule_version=rule_version,
                    query=query,
                    top_k=_MAX_EVIDENCE,
                )
                for hit in hits:
                    sample_id = _text(hit.get("sample_id"))
                    if not sample_id or sample_id in emitted_sample_ids:
                        continue
                    public_item = self._to_public_evidence(hit, scene_code=scene_code)
                    if not public_item:
                        continue
                    evidence.append(public_item)
                    emitted_sample_ids.add(sample_id)
                    if len(evidence) >= _MAX_EVIDENCE:
                        return evidence
            return evidence
        except Exception:
            # 历史案例只作复核参考。检索、向量库或配置异常不得阻断预审。
            logger.warning(
                "费用预审历史案例检索失败 tenant_id=%s claim_id=%s",
                tenant,
                _text(claim.id),
                exc_info=True,
            )
            return []

    def _build_retriever(self) -> FewShotRetriever | None:
        """只读加载 embedding 配置，禁止在费用事务中触发配置初始化提交。"""

        model_row = self.db.get(SystemModelSetting, "embedding")
        if model_row is None or not model_row.enabled:
            return None
        encrypted_api_key = _text(model_row.api_key_encrypted)
        try:
            api_key = decrypt_secret(encrypted_api_key) if encrypted_api_key else ""
        except ValueError:
            logger.warning("embedding 配置密钥无法解密，历史案例检索已跳过")
            return None
        provider = EmbeddingProvider(
            RuntimeModelConfig(
                slot="embedding",
                provider=_text(model_row.provider),
                model=_text(model_row.model_name),
                endpoint=_text(model_row.endpoint),
                api_key=api_key,
                capability=_text(model_row.capability) or "embedding",
            )
        )
        return FewShotRetriever(FewShotStore(provider), self.db)

    @staticmethod
    def _rule_contexts(
        findings: list[dict[str, Any]],
    ) -> list[tuple[str, str]]:
        contexts: list[tuple[str, str]] = []
        for finding in findings:
            context = (
                _text(finding.get("rule_code")),
                _text(finding.get("rule_version")),
            )
            if context != ("", "") and context not in contexts:
                contexts.append(context)
            if len(contexts) >= _MAX_RULE_CONTEXTS:
                break
        # 无规则命中时仍按租户和业务场景检索，但显式传递空的规则标识。
        return contexts or [("", "")]

    @staticmethod
    def _build_query(
        claim: ExpenseClaim,
        *,
        findings: list[dict[str, Any]],
    ) -> str:
        parts = [
            _text(claim.expense_type),
            _text(claim.reason),
            _text(claim.location),
            *[
                _text(finding.get("message"))
                for finding in findings
                if _text(finding.get("message"))
            ],
        ]
        return "\n".join(part for part in parts if part).strip()

    @staticmethod
    def _to_public_evidence(
        hit: dict[str, Any],
        *,
        scene_code: str,
    ) -> dict[str, Any]:
        label = _text(hit.get("label")).lower()
        label_text = _LABEL_TEXT.get(label)
        summary = _SUMMARY_BY_LABEL.get(label)
        if not label_text or not summary:
            return {}
        return {
            "label": label,
            "label_text": label_text,
            "advisory_only": True,
            "score": round(float(hit.get("score") or 0.0), 4),
            "scene_code": _text(hit.get("scene")) or scene_code,
            "policy_ref": _text(hit.get("policy_ref")),
            "rule_version": _text(hit.get("rule_version")),
            "version_status": (
                "stale" if bool(hit.get("stale")) else "matched"
            ),
            "stale": bool(hit.get("stale")),
            "summary": summary,
        }


def _text(value: Any) -> str:
    return str(value or "").strip()


def build_user_agent_historical_evidence_notice(claim: ExpenseClaim) -> str:
    """生成脱敏的 User Agent 提示，不读取或回显历史案例原文。"""

    flags = claim.risk_flags_json
    if isinstance(flags, dict):
        flags = [flags]
    if not isinstance(flags, list):
        return ""
    labels: list[str] = []
    for flag in flags:
        if (
            not isinstance(flag, dict)
            or _text(flag.get("source")) != "ai_pre_review"
        ):
            continue
        for item in list(flag.get("historical_case_evidence") or []):
            if not isinstance(item, dict):
                continue
            label_text = _LABEL_TEXT.get(_text(item.get("label")).lower(), "")
            if label_text and label_text not in labels:
                labels.append(label_text)
    return "历史案例参考：" + "；".join(labels) if labels else ""
