from __future__ import annotations

from typing import Any

EXPENSE_APPLICATION_BUSINESS_STAGE = "expense_application"
REIMBURSEMENT_BUSINESS_STAGE = "reimbursement"
SUPPORTED_RISK_BUSINESS_STAGES = {
    EXPENSE_APPLICATION_BUSINESS_STAGE,
    REIMBURSEMENT_BUSINESS_STAGE,
}
SUPPORTED_RISK_DOMAINS = {
    "budget",
    "policy",
    "invoice",
    "trip",
    "amount",
    "workflow",
    "profile",
}
SUPPORTED_RISK_VISIBILITY_SCOPES = {
    "submitter",
    "leader",
    "budget_manager",
    "finance",
    "admin",
}
SUPPORTED_RISK_ACTIONABILITIES = {
    "fixable_by_submitter",
    "review_decision",
    "route_review",
    "budget_governance",
    "finance_check",
    "system_trace",
}
_NON_RISK_SYSTEM_SOURCES = {
    "application_detail",
    "application_handoff",
    "application_submission",
    "approval",
    "approval_log",
    "approval_routing",
    "budget_approval",
    "expense_claim_approval",
    "expense_claim_finance_approval",
    "finance_approval",
    "manual_approval",
    "payment",
    "sla_reminder",
    "reminder",
    "urge",
}


def normalize_risk_business_stage(value: Any, default: str = REIMBURSEMENT_BUSINESS_STAGE) -> str:
    normalized = str(value or "").strip().lower()
    if normalized in SUPPORTED_RISK_BUSINESS_STAGES:
        return normalized
    return default


def normalize_risk_domain(value: Any, default: str = "policy") -> str:
    normalized = str(value or "").strip().lower()
    if normalized in SUPPORTED_RISK_DOMAINS:
        return normalized
    return default


def normalize_risk_visibility_scope(value: Any, default: str = "leader") -> str:
    normalized = str(value or "").strip().lower()
    if normalized in SUPPORTED_RISK_VISIBILITY_SCOPES:
        return normalized
    return default


def normalize_risk_actionability(value: Any, default: str = "review_decision") -> str:
    normalized = str(value or "").strip().lower()
    if normalized in SUPPORTED_RISK_ACTIONABILITIES:
        return normalized
    return default


def risk_business_stage_for_claim(*, is_application_claim: bool) -> str:
    return (
        EXPENSE_APPLICATION_BUSINESS_STAGE if is_application_claim else REIMBURSEMENT_BUSINESS_STAGE
    )


def build_shadow_release_evaluation(
    manifest: dict[str, Any],
    flag: dict[str, Any] | None,
) -> dict[str, Any]:
    """生成不含业务正文的 shadow 运行摘要。"""

    return {
        "asset_id": str(manifest.get("_rule_asset_id") or ""),
        "rule_code": str(manifest.get("rule_code") or ""),
        "rule_version": str(manifest.get("_rule_version") or ""),
        "release_stage": str(manifest.get("_release_stage") or "shadow"),
        "hit": flag is not None,
        "severity": str((flag or {}).get("severity") or "none"),
    }


def risk_manifest_matches_business_stage(
    manifest: dict[str, Any],
    *,
    business_stage: str | None,
) -> bool:
    """判断规则声明的业务阶段是否覆盖当前申请或报销流程。"""

    if not business_stage:
        return True
    applies_to = manifest.get("applies_to")
    applies_to = applies_to if isinstance(applies_to, dict) else {}
    raw_stages = applies_to.get("business_stages")
    if not isinstance(raw_stages, list):
        metadata = manifest.get("metadata")
        metadata = metadata if isinstance(metadata, dict) else {}
        raw_stages = [
            manifest.get("business_stage")
            or metadata.get("business_stage")
            or REIMBURSEMENT_BUSINESS_STAGE
        ]
    stages = {normalize_risk_business_stage(item) for item in raw_stages if str(item or "").strip()}
    return business_stage in (stages or {REIMBURSEMENT_BUSINESS_STAGE})


def risk_flag_business_stage(flag: dict[str, Any], default: str = "") -> str:
    return normalize_risk_business_stage(
        flag.get("business_stage")
        or flag.get("businessStage")
        or flag.get("control_stage")
        or flag.get("controlStage"),
        default=default,
    )


def infer_risk_domain(flag: dict[str, Any]) -> str:
    explicit_domain = flag.get("risk_domain") or flag.get("riskDomain") or flag.get("domain")
    if explicit_domain:
        return normalize_risk_domain(explicit_domain)

    source = str(flag.get("source") or "").strip().lower()
    event_type = str(flag.get("event_type") or flag.get("eventType") or "").strip().lower()
    if source == "budget_control" or "budget" in event_type:
        return "budget"
    if source in {"manual_return", "approval_routing"}:
        return "workflow"
    if source in {"attachment_analysis"}:
        return "invoice"
    if source in {"financial_risk_graph"}:
        return "profile"

    corpus = " ".join(
        str(value or "")
        for value in [
            flag.get("rule_code"),
            flag.get("risk_category"),
            flag.get("ontology_signal"),
            flag.get("label"),
            flag.get("name"),
            flag.get("title"),
            flag.get("message"),
            flag.get("summary"),
            flag.get("description"),
        ]
    ).lower()
    if any(token in corpus for token in ["预算", "budget"]):
        return "budget"
    if any(
        token in corpus for token in ["发票", "票据", "单据", "附件", "ocr", "invoice", "receipt"]
    ):
        return "invoice"
    trip_tokens = [
        "行程",
        "城市",
        "住宿",
        "交通",
        "差旅",
        "酒店",
        "日期",
        "时间",
        "trip",
        "travel",
        "city",
        "hotel",
        "transport",
        "period",
    ]
    if any(token in corpus for token in trip_tokens):
        return "trip"
    if any(
        token in corpus
        for token in ["金额", "超标", "阈值", "额度", "标准", "amount", "limit", "over"]
    ):
        return "amount"
    if any(token in corpus for token in ["历史", "画像", "异常关系", "profile", "baseline"]):
        return "profile"
    if any(
        token in corpus
        for token in ["审批", "退回", "流程", "付款", "routing", "approval", "return", "payment"]
    ):
        return "workflow"
    return "policy"


def infer_risk_semantics(
    flag: dict[str, Any],
    *,
    business_stage: str,
) -> tuple[str, str, str]:
    risk_domain = infer_risk_domain(flag)
    source = str(flag.get("source") or "").strip().lower()

    if source in _NON_RISK_SYSTEM_SOURCES:
        return risk_domain, "admin", "system_trace"
    if risk_domain == "budget":
        return risk_domain, "budget_manager", "budget_governance"
    if source == "attachment_analysis":
        return risk_domain, "submitter", "fixable_by_submitter"
    if risk_domain == "profile":
        return risk_domain, "leader", "review_decision"
    if business_stage == REIMBURSEMENT_BUSINESS_STAGE:
        if risk_domain in {"policy", "invoice", "trip", "amount"}:
            return risk_domain, "submitter", "fixable_by_submitter"
        return risk_domain, "finance", "finance_check"
    if business_stage == EXPENSE_APPLICATION_BUSINESS_STAGE:
        # 申请单阶段：信息完整性/差旅/金额类风险申请人可自行补充修正，
        # 对申请人可见；其余（如画像、审批流程）仍走领导可见。
        if risk_domain in {"policy", "trip", "amount"}:
            return risk_domain, "submitter", "fixable_by_submitter"
        return risk_domain, "leader", "review_decision"
    return risk_domain, "leader", "review_decision"


def enrich_risk_flag_semantics(
    flag: dict[str, Any],
    *,
    business_stage: str | None = None,
    risk_domain: str | None = None,
    visibility_scope: str | None = None,
    actionability: str | None = None,
) -> dict[str, Any]:
    stage = normalize_risk_business_stage(
        business_stage
        or flag.get("business_stage")
        or flag.get("businessStage")
        or flag.get("control_stage")
        or flag.get("controlStage")
    )
    inferred_domain, inferred_scope, inferred_actionability = infer_risk_semantics(
        flag,
        business_stage=stage,
    )
    domain = normalize_risk_domain(
        risk_domain or flag.get("risk_domain") or flag.get("riskDomain"), inferred_domain
    )
    scope = normalize_risk_visibility_scope(
        visibility_scope or flag.get("visibility_scope") or flag.get("visibilityScope"),
        inferred_scope,
    )
    action = normalize_risk_actionability(
        actionability or flag.get("actionability"),
        inferred_actionability,
    )

    return {
        **flag,
        "business_stage": stage,
        "businessStage": stage,
        "risk_domain": domain,
        "visibility_scope": scope,
        "actionability": action,
    }


def with_risk_business_stage(
    flag: dict[str, Any],
    business_stage: str,
) -> dict[str, Any]:
    return enrich_risk_flag_semantics(flag, business_stage=business_stage)
