"""KPI数据治理4条规则：入库必检 + 元数据必填 + 编码规范 + 战略分级

规则1: 入库必检 — dimension/target_value/unit 必填（创建/更新强制拦截）
规则2: 元数据必填 — formula/data_source/data_owner 不能为空或占位符（待补充/待指定/-）
规则3: 编码规范 — kpi_code 必须以 F_/C_/P_/L_ 前缀开头且与 dimension 一致；禁止跨层同名
规则4: 战略/运营分级 — kpi_level 必须是 strategic/operational

API:
- POST /api/cma/kpi/validate           校验单个KPI数据 → {valid, errors}
- GET  /api/cma/kpi/governance/audit   全量校验 → 按4条规则分组的不合规清单
"""
from typing import List, Optional

from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session

from app.database import get_db
from app.auth_middleware import require_auth, require_role
from app.models import KPIDefinition

router = APIRouter(
    prefix="/api/cma/kpi",
    tags=["KPI数据治理"],
    dependencies=[Depends(require_role("ceo", "finance", "business", "it"))],
)

# 维度 → 编码前缀
DIM_PREFIX = {"finance": "F", "customer": "C", "process": "P", "learning": "L"}
# 历史遗留兼容前缀: EXT_ = 科目余额表导入的财务科目KPI（仅限 finance 维度）
LEGACY_PREFIX_DIM = {"EXT": "finance", "FB": "process"}
# 品牌级业务KPI前缀（跨维度合法，2026-08-21 治理审计适配: BH_ = 博海业务KPI系列）
BRAND_PREFIXES = ("BH",)
VALID_LEVELS = ("strategic", "operational")
# 视为"未完善"的占位符值
PLACEHOLDERS = ("待补充", "待指定", "待完善", "待定", "暂无", "TBD", "tbd", "-", "--", "N/A", "n/a")

# 规则2必填元数据字段
META_FIELDS = [
    ("formula", "计算公式"),
    ("data_source", "数据来源"),
    ("data_owner", "数据责任人"),
    ("kpi_name", "KPI名称"),
]


def _clean_str(val) -> str:
    if val is None:
        return ""
    if isinstance(val, str):
        return val.strip()
    return str(val).strip()


def _is_placeholder(val) -> bool:
    """空值或占位符（待补充/待指定/- 等）视为未完善"""
    s = _clean_str(val)
    if not s:
        return True
    return s in PLACEHOLDERS


def validate_kpi_payload(
    data: dict,
    db: Session = None,
    current_kpi_id: Optional[int] = None,
    is_update: bool = False,
) -> List[dict]:
    """校验单个KPI数据（4条规则）。

    - data: 提交的KPI字段字典（创建或更新的载荷）
    - db: SQLAlchemy Session（用于跨层同名/编码唯一性检查，可为None）
    - current_kpi_id: 更新时传KPI自身id（避免自检误报）
    - is_update: 更新模式 — 仅校验载荷中显式出现的字段

    返回 [{rule: int, field: str, message: str}, ...]，空列表=合规。
    """
    issues: List[dict] = []

    def add(rule: int, field: str, message: str):
        issues.append({"rule": rule, "field": field, "message": message})

    code = _clean_str(data.get("kpi_code"))
    dimension = _clean_str(data.get("dimension"))
    has_code = bool(code)

    # ── 规则1: 入库必检 dimension/target_value/unit ──
    if not is_update or "dimension" in data:
        if not dimension:
            add(1, "dimension", "缺少dimension(所属维度: finance/customer/process/learning)")
    if not is_update or "target_value" in data:
        tv = data.get("target_value")
        if tv is None or (isinstance(tv, str) and _clean_str(tv) == ""):
            add(1, "target_value", "缺少target_value(目标值)")
    if not is_update or "unit" in data:
        if _is_placeholder(data.get("unit")):
            add(1, "unit", "缺少unit(单位)")

    # ── 规则2: 元数据必填（不能为空或占位符）──
    for field, label in META_FIELDS:
        if not is_update or field in data:
            if _is_placeholder(data.get(field)):
                add(2, field, f"元数据未完善: {label}({field})不能为空或占位符(待补充/待指定/-)")

    # ── 规则3: 编码规范 ──
    if not is_update or "kpi_code" in data:
        if not has_code:
            add(3, "kpi_code", "缺少kpi_code(KPI编码)")
        else:
            prefix = code.split("_")[0] if "_" in code else code
            if prefix not in ("F", "C", "P", "L"):
                # 兼容历史遗留 EXT_ 前缀（科目余额表导入的财务科目KPI，仅限finance维度）
                # 兼容 FB_ 前缀（财务Bot KPI，仅限process维度）与 BH_ 品牌前缀（跨维度合法）
                if not (LEGACY_PREFIX_DIM.get(prefix) and dimension == LEGACY_PREFIX_DIM[prefix]) \
                        and prefix not in BRAND_PREFIXES:
                    add(3, "kpi_code", f"编码前缀不符: {code} 应以F_/C_/P_/L_开头")
            elif dimension and DIM_PREFIX.get(dimension) and prefix != DIM_PREFIX[dimension]:
                expected = DIM_PREFIX[dimension]
                add(3, "kpi_code",
                    f"编码前缀与维度不符: {code} 前缀{prefix}_ 与维度{dimension}(应为{expected}_)不一致")

    # 禁止跨层同名: 同一kpi_code不能用于不同dimension
    if has_code and db is not None:
        dup = db.query(KPIDefinition).filter(KPIDefinition.kpi_code == code).first()
        if dup and dup.id != current_kpi_id:
            if dimension and dup.dimension and dup.dimension != dimension:
                add(3, "kpi_code",
                    f"跨层同名: {code} 已用于维度{dup.dimension}，不能用于维度{dimension}")
            elif not dimension:
                add(3, "kpi_code", f"编码已存在: {code} 已注册(维度{dup.dimension})，不能重复使用")

    # ── 规则4: 战略/运营分级 ──
    if not is_update or "kpi_level" in data:
        lv = data.get("kpi_level")
        if lv is not None and lv not in VALID_LEVELS:
            add(4, "kpi_level", f"kpi_level必须是strategic或operational，当前值: {lv}")

    return issues


def kpi_issues_message(issues: List[dict]) -> List[str]:
    """issue dict列表 → 纯文本错误列表"""
    return [i["message"] for i in issues]


@router.post("/validate")
def validate_kpi(
    kpi_data: dict,
    db: Session = Depends(get_db),
    current_user=Depends(require_auth),
):
    """校验单个KPI数据（不入库）。

    请求体: KPI字段字典，可选 kpi_id 标识正在编辑的KPI（避免跨层同名误报）。
    返回: {"valid": bool, "errors": [str], "details": [{rule, field, message}]}
    """
    kpi_id = kpi_data.get("kpi_id") if isinstance(kpi_data.get("kpi_id"), int) else None
    issues = validate_kpi_payload(kpi_data, db=db, current_kpi_id=kpi_id)
    return {
        "valid": len(issues) == 0,
        "errors": kpi_issues_message(issues),
        "details": issues,
    }


@router.get("/governance/audit")
def governance_audit(
    db: Session = Depends(get_db),
    current_user=Depends(require_auth),
):
    """全量校验所有活跃KPI，输出按4条规则分组的不合规清单。"""
    kpis = db.query(KPIDefinition).filter(KPIDefinition.status == "active").all()

    non_compliant = []
    by_rule: dict = {1: [], 2: [], 3: [], 4: []}
    for k in kpis:
        payload = {c.name: getattr(k, c.name) for c in k.__table__.columns}
        issues = validate_kpi_payload(payload, db=db, current_kpi_id=k.id)
        if issues:
            entry = {
                "kpi_id": k.id,
                "kpi_code": k.kpi_code,
                "kpi_name": k.kpi_name,
                "dimension": k.dimension,
                "kpi_level": k.kpi_level,
                "issues": issues,
            }
            non_compliant.append(entry)
            for i in issues:
                by_rule.setdefault(i["rule"], []).append({
                    "kpi_id": k.id,
                    "kpi_code": k.kpi_code,
                    "kpi_name": k.kpi_name,
                    "field": i["field"],
                    "message": i["message"],
                })

    rule_counts = {str(r): len(items) for r, items in by_rule.items()}
    return {
        "total": len(kpis),
        "compliant": len(kpis) - len(non_compliant),
        "non_compliant_count": len(non_compliant),
        "rule_counts": rule_counts,
        "by_rule": {str(r): items for r, items in by_rule.items()},
        "non_compliant": non_compliant,
    }
