from __future__ import annotations

import re
from typing import Any

from app.core.agent_enums import AgentAssetDomain
from app.services.risk_rule_generation_interpreter import COMPOSITE_RULE_TEMPLATE_KEY
from app.services.risk_rule_generation_ontology import (
    DOMAIN_FIELD_PREFIXES,
    EXPENSE_BUSINESS_STAGE_LABELS,
    EXPENSE_RISK_CATEGORY_ALIASES,
    EXPENSE_RISK_CATEGORY_LABELS,
    FIELD_ONTOLOGY,
    RiskRuleField,
)
from app.services.risk_rule_generation_semantics import (
    CITY_CONSISTENCY_SEMANTIC_TYPES,
    build_city_consistency_draft,
)


class RiskRuleGenerationFieldMixin:
    @staticmethod
    def _normalize_expense_category(value: str | None, domain: str) -> str | None:
        if domain != AgentAssetDomain.EXPENSE.value:
            return None

        normalized = str(value or "").strip().lower()
        if not normalized:
            return None

        normalized = EXPENSE_RISK_CATEGORY_ALIASES.get(normalized, normalized)
        if normalized not in EXPENSE_RISK_CATEGORY_LABELS:
            allowed = "、".join(EXPENSE_RISK_CATEGORY_LABELS.values())
            raise ValueError(f"费用领域仅支持：{allowed}。")
        return normalized

    @staticmethod
    def _normalize_business_stage(value: str | None, domain: str) -> str:
        if domain != AgentAssetDomain.EXPENSE.value:
            return "reimbursement"

        normalized = str(value or "reimbursement").strip().lower()
        if not normalized:
            normalized = "reimbursement"
        if normalized not in EXPENSE_BUSINESS_STAGE_LABELS:
            allowed = "、".join(EXPENSE_BUSINESS_STAGE_LABELS.values())
            raise ValueError(f"业务环节仅支持：{allowed}。")
        return normalized

    def _resolve_fields(self, text: str, *, domain: str) -> list[RiskRuleField]:
        prefixes = DOMAIN_FIELD_PREFIXES.get(domain, ())
        candidates = [field for field in FIELD_ONTOLOGY if field.key.startswith(prefixes)]
        normalized = text.lower()
        matched: list[tuple[int, RiskRuleField]] = []
        for field in candidates:
            score = self._score_field_match(field, text, normalized)
            if score > 0:
                matched.append((score, field))

        if domain == AgentAssetDomain.EXPENSE.value:
            if any(keyword in text for keyword in ("住宿", "酒店", "行程", "城市", "出差")):
                matched.extend(
                    (10, field)
                    for field in candidates
                    if field.key
                    in {
                        "claim.reason",
                        "claim.location",
                        "item.item_date",
                        "item.item_reason",
                        "item.item_location",
                        "attachment.hotel_city",
                        "attachment.route_cities",
                        "attachment.issue_date",
                        "attachment.stay_start_date",
                        "attachment.stay_end_date",
                    }
                )
            if any(keyword in text for keyword in ("发票", "票据", "品名", "抬头", "开票")):
                matched.extend(
                    (6, field)
                    for field in candidates
                    if field.key
                    in {
                        "attachment.invoice_no",
                        "attachment.buyer_name",
                        "attachment.goods_name",
                        "attachment.ocr_text",
                    }
                )

        matched.sort(key=lambda item: item[0], reverse=True)
        deduped: list[RiskRuleField] = []
        seen: set[str] = set()
        for _, field in matched:
            if field.key in seen:
                continue
            seen.add(field.key)
            deduped.append(field)
        if deduped:
            return deduped[:10]
        return candidates[:4]

    @staticmethod
    def _score_field_match(field: RiskRuleField, text: str, normalized: str) -> int:
        score = 0
        if field.label in text:
            score += 8
        for alias in field.aliases:
            if alias.lower() in normalized:
                score += 4 + min(len(alias), 6)

        if field.key == "attachment.hotel_city" and any(term in text for term in ("酒店", "住宿")):
            score += 12
        if field.key == "attachment.route_cities" and any(
            term in text for term in ("行程", "交通票", "路线", "途经")
        ):
            score += 10
        if field.key in {
            "claim.trip_start_date",
            "claim.trip_end_date",
            "item.item_date",
            "attachment.stay_start_date",
            "attachment.stay_end_date",
        } and any(
            term in text for term in ("日期", "时间", "出差开始", "出差结束", "入住", "离店")
        ):
            score += 10
        if field.key == "claim.location" and any(
            term in text for term in ("申报目的地", "申报地点", "目的地", "出差地")
        ):
            score += 10
        if field.key.startswith("attachment.") and any(term in text for term in ("发票", "票据")):
            score += 2
        return score

    def _align_draft_fields(
        self,
        draft: dict[str, Any],
        *,
        natural_language: str,
        risk_level: str,
        fields: list[RiskRuleField],
    ) -> dict[str, Any]:
        if str(draft.get("semantic_type") or "").strip() in CITY_CONSISTENCY_SEMANTIC_TYPES:
            return build_city_consistency_draft(
                draft,
                natural_language=natural_language,
                fields=fields,
                risk_level=risk_level,
            )

        field_by_key = {field.key: field for field in fields}
        original_keys = [
            str(item or "").strip()
            for item in list(draft.get("field_keys") or [])
            if str(item or "").strip() in field_by_key
        ]
        if draft.get("template_key") == COMPOSITE_RULE_TEMPLATE_KEY:
            return {**draft, "field_keys": original_keys or [field.key for field in fields[:8]]}

        preferred_keys: list[str] = []

        def add_preferred(key: str, *terms: str) -> None:
            if key in field_by_key and any(term in natural_language for term in terms):
                preferred_keys.append(key)

        add_preferred("attachment.hotel_city", "酒店", "住宿")
        add_preferred("claim.location", "申报目的地", "申报地点", "目的地", "出差地")
        add_preferred("attachment.route_cities", "行程", "交通票", "路线", "途经")

        merged_keys: list[str] = []
        for key in [*preferred_keys, *original_keys, *[field.key for field in fields]]:
            if key in field_by_key and key not in merged_keys:
                merged_keys.append(key)
            if len(merged_keys) >= 4:
                break

        if draft.get("template_key") == "field_compare_v1" and len(merged_keys) < 2:
            for field in fields:
                if field.key not in merged_keys:
                    merged_keys.append(field.key)
                if len(merged_keys) >= 2:
                    break

        aligned = {**draft, "field_keys": merged_keys}
        selected_fields = [field_by_key[key] for key in merged_keys if key in field_by_key]
        if selected_fields:
            aligned["condition_summary"] = self._build_condition_summary(
                natural_language,
                template_key=str(aligned.get("template_key") or "field_required_v1"),
                fields=selected_fields,
            )
            flow = aligned.get("flow") if isinstance(aligned.get("flow"), dict) else {}
            aligned["flow"] = {
                **flow,
                "evidence": "读取" + "、".join(field.label for field in selected_fields[:3]),
                "decision": aligned["condition_summary"],
            }
        return aligned

    @staticmethod
    def _build_compare_conditions(field_keys: list[str]) -> list[dict[str, str]]:
        if len(field_keys) >= 2:
            return [{"left": field_keys[0], "operator": "overlap", "right": field_keys[1]}]
        if field_keys:
            return [{"left": field_keys[0], "operator": "is_empty", "right": ""}]
        return []

    @staticmethod
    def _infer_template_key(text: str) -> str:
        if any(
            keyword in text
            for keyword in ("超过", "超出", "超预算", "预算", "阈值", "早于", "晚于", "范围")
        ):
            return COMPOSITE_RULE_TEMPLATE_KEY
        if any(
            keyword in text
            for keyword in ("一致", "匹配", "相同", "不一致", "不符", "对应", "出现在")
        ):
            return "field_compare_v1"
        if any(
            keyword in text
            for keyword in ("关键词", "包含", "出现", "品名", "摘要", "服务费", "咨询费")
        ):
            return "keyword_match_v1"
        return "field_required_v1"

    @staticmethod
    def _infer_keywords(text: str) -> list[str]:
        quoted = re.findall(r"[“\"']([^“”\"']{2,20})[”\"']", text)
        keywords = [item.strip() for item in quoted if item.strip()]
        for candidate in ("咨询费", "服务费", "其他", "办公用品", "招待", "红冲", "作废"):
            if candidate in text and candidate not in keywords:
                keywords.append(candidate)
        return keywords[:8]

    @staticmethod
    def _infer_rule_name(text: str) -> str:
        normalized = re.sub(r"\s+", "", str(text or ""))
        normalized = re.sub(r"[，。；;：:、,.!?！？]", "", normalized)
        if not normalized:
            return "自然语言风险规则"
        return f"{normalized[:18]}风险规则"

    @staticmethod
    def _build_condition_summary(
        natural_language: str,
        *,
        template_key: str,
        fields: list[RiskRuleField],
    ) -> str:
        field_text = "、".join(item.label for item in fields[:3]) or "业务字段"
        if template_key == "field_compare_v1":
            return f"对比{field_text}之间是否一致或存在交集"
        if template_key == "keyword_match_v1":
            return f"检查{field_text}是否出现规则描述中的风险关键词"
        return f"检查{field_text}是否满足必填和完整性要求"

    @staticmethod
    def _clean_text(value: Any) -> str:
        return re.sub(r"\s+", " ", str(value or "")).strip()

    @staticmethod
    def _extract_json_object(text: str) -> str:
        normalized = re.sub(
            r"^```(?:json)?|```$",
            "",
            str(text or "").strip(),
            flags=re.IGNORECASE,
        )
        start = normalized.find("{")
        end = normalized.rfind("}")
        if start < 0 or end <= start:
            raise ValueError("JSON object not found.")
        return normalized[start : end + 1]
