from __future__ import annotations

from datetime import UTC, datetime

from sqlalchemy import select

from app.core.agent_enums import (
    AgentAssetContentType,
    AgentAssetDomain,
    AgentAssetStatus,
    AgentAssetType,
    AgentReviewStatus,
)
from app.core.logging import get_logger
from app.models.agent_asset import AgentAsset
from app.services.agent_asset_access import platform_asset_statement
from app.services.agent_asset_rule_library import AgentAssetRuleLibraryManager
from app.services.agent_asset_spreadsheet import (
    RISK_RULES_LIBRARY,
)
from app.services.agent_foundation_constants import (
    PLATFORM_DESTINATION_LOCATION_RULE_FILENAME,
)
from app.services.risk_rule_manifest_classifier import is_budget_risk_manifest

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

EXPENSE_TYPE_SCENARIO_LABELS = {
    "all": "全部",
    "travel": "差旅费",
    "hotel": "住宿费",
    "transport": "交通费",
    "meal": "业务招待费",
    "meeting": "会务费",
    "marketing": "市场推广费",
    "office": "办公用品费",
    "training": "培训费",
    "software": "软件服务费",
    "communication": "通信费",
    "welfare": "福利费",
}


class AgentFoundationRiskRuleMixin:
    def _iter_platform_risk_manifests(self) -> list[tuple[str, dict[str, object]]]:

        manager = AgentAssetRuleLibraryManager()

        manifests: list[tuple[str, dict[str, object]]] = []

        for file_name in sorted(
            manager.list_rule_library_json_files(library=RISK_RULES_LIBRARY)
        ):

            payload = manager.read_rule_library_json(
                library=RISK_RULES_LIBRARY,
                file_name=file_name,
            )

            if payload.get("enabled") is False:

                continue

            if self._is_user_generated_risk_manifest(payload):

                continue

            if is_budget_risk_manifest(payload):

                continue

            manifests.append((file_name, payload))

        return manifests

    @staticmethod

    def _is_user_generated_risk_manifest(manifest: dict[str, object]) -> bool:

        rule_code = str(manifest.get("rule_code") or "").strip().lower()

        metadata = manifest.get("metadata") if isinstance(manifest.get("metadata"), dict) else {}

        stability = str(metadata.get("stability") or "").strip().lower()

        source_ref = str(metadata.get("source_ref") or "").strip()

        if stability == "generated_draft":

            return True

        if source_ref == "自然语言风险规则":

            return True

        return ".generated_" in rule_code

    @staticmethod

    def _resolve_platform_risk_category(manifest: dict[str, object]) -> str:

        explicit = str(manifest.get("risk_category") or "").strip()

        if explicit:

            return explicit

        rule_code = str(manifest.get("rule_code") or "").strip().lower()

        applies_to = (
            manifest.get("applies_to") if isinstance(manifest.get("applies_to"), dict) else {}
        )

        domains = {str(item or "").strip().lower() for item in applies_to.get("domains") or []}

        expense_types = {

            str(item or "").strip().lower() for item in applies_to.get("expense_types") or []

        }

        if rule_code.startswith("risk.invoice."):

            return "发票"

        if "meal" in domains or "entertainment" in expense_types:

            return "餐饮招待"

        if "transport" in expense_types or "consecutive_transport" in rule_code:

            return "交通出行"

        if "office" in expense_types:

            return "办公物料"

        if "travel" in domains or rule_code.startswith("risk.travel."):

            return "差旅"

        if rule_code.startswith("risk.expense."):

            return "费用科目"

        return "通用"

    @staticmethod
    def _resolve_manifest_expense_types(manifest: dict[str, object]) -> list[str]:
        def _collect(value: object) -> list[str]:
            if isinstance(value, str):
                return [value]
            if isinstance(value, (list, tuple, set)):
                return [str(item or "").strip() for item in value]
            return []

        candidates: list[str] = []
        candidates.extend(_collect(manifest.get("expense_types")))

        applies_to = (
            manifest.get("applies_to") if isinstance(manifest.get("applies_to"), dict) else {}
        )
        candidates.extend(_collect(applies_to.get("expense_types")))

        metadata = manifest.get("metadata") if isinstance(manifest.get("metadata"), dict) else {}
        candidates.extend(_collect(metadata.get("expense_types")))

        all_scope_values = {"all", "*", "overall", "general", "全部", "通用"}
        if any(str(item or "").strip().lower() in all_scope_values for item in candidates):
            return ["all"]

        normalized: list[str] = []
        seen: set[str] = set()
        for item in candidates:
            value = item.strip().lower()
            if not value or value in seen:
                continue
            seen.add(value)
            normalized.append(value)
        return normalized

    @staticmethod
    def _expense_type_scenario_labels(expense_types: list[str]) -> list[str]:
        if any(str(item or "").strip().lower() in {"all", "*", "overall", "general"} for item in expense_types):
            return ["全部"]

        labels: list[str] = []
        seen: set[str] = set()
        for expense_type in expense_types:
            label = EXPENSE_TYPE_SCENARIO_LABELS.get(expense_type)
            if not label or label in seen:
                continue
            seen.add(label)
            labels.append(label)
        return labels

    def _platform_risk_scenario_json(self, manifest: dict[str, object]) -> list[str]:

        labels = self._expense_type_scenario_labels(self._resolve_manifest_expense_types(manifest))
        if labels:
            return labels

        category = self._resolve_platform_risk_category(manifest)

        return [category] if category else ["通用"]

    def _platform_risk_config_json(
        self, file_name: str, manifest: dict[str, object]
    ) -> dict[str, object]:

        outcomes = manifest.get("outcomes") if isinstance(manifest.get("outcomes"), dict) else {}

        fail_outcome = outcomes.get("fail") if isinstance(outcomes.get("fail"), dict) else {}

        risk_category = self._resolve_platform_risk_category(manifest)

        config = {

            "severity": str(fail_outcome.get("severity") or "medium"),

            "enabled": True,

            "tag": "风险规则",

            "detail_mode": "json_risk",

            "risk_category": risk_category,

            "rule_library": RISK_RULES_LIBRARY,

            "rule_document": {

                "file_name": file_name,

                "storage_key": f"rules/{RISK_RULES_LIBRARY}/{file_name}",

            },

            "ontology_signal": str(manifest.get("ontology_signal") or "").strip(),

            "evaluator": str(manifest.get("evaluator") or "").strip(),

            "source_ref": (

                (manifest.get("metadata") or {}).get("source_ref")

                if isinstance(manifest.get("metadata"), dict)

                else ""

            ),

        }
        metadata = manifest.get("metadata") if isinstance(manifest.get("metadata"), dict) else {}
        for key in (
            "finance_rule_code",
            "finance_rule_sheet",
            "business_stage",
            "expense_types",
            "budget_required",
        ):
            value = manifest.get(key)
            if value is None and isinstance(metadata, dict):
                value = metadata.get(key)
            if value is not None:
                config[key] = value
        return config

    def _build_platform_risk_seed_assets(self) -> list[AgentAsset]:

        assets: list[AgentAsset] = []

        for file_name, manifest in self._iter_platform_risk_manifests():

            rule_code = str(manifest.get("rule_code") or "").strip()

            if not rule_code:

                continue

            metadata = (
                manifest.get("metadata") if isinstance(manifest.get("metadata"), dict) else {}
            )

            source_ref = str(metadata.get("source_ref") or "").strip()

            rule_description = str(manifest.get("description") or "").strip()

            assets.append(

                AgentAsset(

                    asset_type=AgentAssetType.RULE.value,

                    code=rule_code,

                    name=str(manifest.get("name") or rule_code),

                    description=rule_description

                    or f"平台通用风险规则：{source_ref or manifest.get('name') or rule_code}",

                    domain=AgentAssetDomain.EXPENSE.value,

                    scenario_json=self._platform_risk_scenario_json(manifest),

                    owner=str(metadata.get("owner") or "风控与审计部"),

                    reviewer="顾承宇",

                    status=AgentAssetStatus.ACTIVE.value,

                    current_version="v1.0.0",

                    published_version="v1.0.0",

                    working_version="v1.0.0",

                    config_json=self._platform_risk_config_json(file_name, manifest),

                )

            )

        return assets

    def sync_platform_risk_rules_from_library(self) -> int:

        existing_codes = set(
            self.db.scalars(
                select(AgentAsset.code).where(
                    AgentAsset.scope == "platform",
                    AgentAsset.tenant_id == "platform",
                )
            ).all()
        )

        before_count = len(existing_codes)

        self._ensure_platform_risk_rules_from_library(existing_codes)
        manifest_codes = {
            str(manifest.get("rule_code") or "").strip()
            for _, manifest in self._iter_platform_risk_manifests()
            if str(manifest.get("rule_code") or "").strip()
        }
        self._hide_stale_demo_risk_rules(manifest_codes)

        self.db.flush()

        after_codes = set(
            self.db.scalars(
                select(AgentAsset.code).where(
                    AgentAsset.scope == "platform",
                    AgentAsset.tenant_id == "platform",
                )
            ).all()
        )

        synced = max(len(after_codes) - before_count, 0)

        manifest_count = len(self._iter_platform_risk_manifests())

        logger.info(

            "Platform risk rules synced from library",

            extra={
                "manifest_count": manifest_count,
                "created_count": synced,
                "total": len(after_codes),
            },

        )

        return manifest_count

    def _hide_stale_demo_risk_rules(self, manifest_codes: set[str]) -> None:
        assets = self.db.scalars(
            platform_asset_statement().where(
                AgentAsset.asset_type == AgentAssetType.RULE.value
            )
        ).all()
        for asset in assets:
            config = asset.config_json if isinstance(asset.config_json, dict) else {}
            if config.get("source_ref") != "费用管控 Demo 风险规则库":
                continue
            if asset.code in manifest_codes:
                continue
            asset.status = AgentAssetStatus.DISABLED.value
            asset.config_json = {
                **config,
                "enabled": False,
                "tag": "废弃风险规则",
                "deprecated": True,
                "deprecated_reason": "对应风险规则 JSON 已删除，不再参与费用管控 Demo。",
            }

    def _ensure_platform_risk_rules_from_library(self, existing_codes: set[str]) -> None:

        for file_name, manifest in self._iter_platform_risk_manifests():

            rule_code = str(manifest.get("rule_code") or "").strip()

            if not rule_code:

                continue

            metadata = (
                manifest.get("metadata") if isinstance(manifest.get("metadata"), dict) else {}
            )

            source_ref = str(metadata.get("source_ref") or "").strip()

            rule_description = str(manifest.get("description") or "").strip()

            config_json = self._platform_risk_config_json(file_name, manifest)

            scenario_json = self._platform_risk_scenario_json(manifest)

            asset = self.db.scalar(
                platform_asset_statement().where(AgentAsset.code == rule_code)
            )

            if asset is None and rule_code not in existing_codes:

                asset = self._create_seed_asset(

                    asset_type=AgentAssetType.RULE.value,

                    code=rule_code,

                    name=str(manifest.get("name") or rule_code),

                    description=rule_description

                    or f"平台通用风险规则：{source_ref or manifest.get('name') or rule_code}",

                    domain=AgentAssetDomain.EXPENSE.value,

                    scenario_json=scenario_json,

                    owner=str(metadata.get("owner") or "风控与审计部"),

                    reviewer="顾承宇",

                    status=AgentAssetStatus.ACTIVE.value,

                    current_version="v1.0.0",

                    config_json=config_json,

                )

            if asset is None:

                continue

            if not str(asset.current_version or "").strip():

                asset.current_version = "v1.0.0"

            if not str(asset.working_version or "").strip():

                asset.working_version = asset.current_version

            if not str(asset.published_version or "").strip():

                asset.published_version = asset.current_version

            asset.status = asset.status or AgentAssetStatus.ACTIVE.value

            asset.name = str(manifest.get("name") or asset.name or rule_code)

            if rule_description:

                asset.description = rule_description

            asset.config_json = config_json

            asset.scenario_json = scenario_json

            self._ensure_asset_version(

                asset,

                version="v1.0.0",

                content=self._platform_risk_rule_markdown(
                    asset,
                    manifest=manifest,
                    file_name=file_name,
                ),

                content_type=AgentAssetContentType.MARKDOWN.value,

                change_note=f"平台通用风险规则：{asset.name}",

                created_by="系统初始化",

            )

            self._ensure_asset_review(

                asset,

                version="v1.0.0",

                reviewer="顾承宇",

                review_status=AgentReviewStatus.APPROVED.value,

                review_note="平台内置风险规则，供提交验审与风险问答共用。",

                reviewed_at=datetime.now(UTC),

            )

    @staticmethod

    def _platform_risk_rule_markdown(

        asset: AgentAsset,

        *,

        manifest: dict[str, object] | None = None,

        file_name: str = "",

    ) -> str:

        config = asset.config_json if isinstance(asset.config_json, dict) else {}

        rule_document = (
            config.get("rule_document") if isinstance(config.get("rule_document"), dict) else {}
        )

        resolved_file_name = file_name or str(rule_document.get("file_name") or "").strip()

        evaluator = str(config.get("evaluator") or (manifest or {}).get("evaluator") or "").strip()

        ontology_signal = str(
            config.get("ontology_signal") or (manifest or {}).get("ontology_signal") or ""
        ).strip()

        source_ref = str(config.get("source_ref") or "").strip()

        if not source_ref and isinstance(manifest, dict):

            metadata = (
                manifest.get("metadata") if isinstance(manifest.get("metadata"), dict) else {}
            )

            source_ref = str(metadata.get("source_ref") or "").strip()

        lines = [

            f"# {asset.name}",

            "",

            "## 规则类型",

            "",

            "- 平台内置通用风险规则（`json_risk`）",

        ]

        if evaluator:

            lines.append(f"- 检查器：`{evaluator}`")

        if ontology_signal:

            lines.append(f"- 本体信号：`{ontology_signal}`")

        if source_ref:

            lines.extend(["", "## 来源", "", f"- {source_ref}"])

        if resolved_file_name:

            lines.extend(

                [

                    "",

                    "## 配置文件",

                    "",

                    f"- `rules/{RISK_RULES_LIBRARY}/{resolved_file_name}`",

                ]

            )

        return "\n".join(lines)

    @staticmethod

    def _platform_destination_location_risk_markdown() -> str:

        return AgentFoundationRiskRuleMixin._platform_risk_rule_markdown(

            AgentAsset(
                name="申报地点与票据地点一致",
                config_json={"evaluator": "location_consistency"},
            ),

            manifest={

                "evaluator": "location_consistency",

                "ontology_signal": "location_mismatch",

                "metadata": {"source_ref": "常用risk.txt / 一、出差类 / 行程不符"},

            },

            file_name=PLATFORM_DESTINATION_LOCATION_RULE_FILENAME,

        )
