"""战略地图 API"""
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import Optional
from datetime import date, datetime, timedelta
from app.database import get_db
from app.deps import get_entity_id
from app.auth_middleware import require_auth, require_role
from app.models import StrategicMap, OperationLog, MapObjective, Objective, KR, KPIDefinition, ReviewRecord, User
import json

router = APIRouter(prefix="/api/cma/maps", tags=["战略地图"],
    dependencies=[Depends(require_role("ceo", "finance"))],
)

# ── 四维度模板 ──────────────────────────────
STRATEGIC_MAP_TEMPLATE = [
    {
        "key": "finance",
        "name": "财务层",
        "icon": "💰",
        "color": "#F56C6C",
        "objectives": [
            {"name": "营收目标", "kpis": ["F_REVENUE"]},
            {"name": "净利润率", "kpis": ["F_NET_PROFIT"]},
            {"name": "现金流", "kpis": ["F_OP_CFLOW"]},
        ],
    },
    {
        "key": "customer",
        "name": "客户层",
        "icon": "👥",
        "color": "#409EFF",
        "objectives": [
            {"name": "客户满意度", "kpis": ["C_SATISFACTION"]},
            {"name": "市场份额", "kpis": ["C_MARKET_SHARE"]},
            {"name": "客户保留率", "kpis": ["C_RETENTION_RATE"]},
        ],
    },
    {
        "key": "process",
        "name": "内部流程层",
        "icon": "⚙️",
        "color": "#67C23A",
        "objectives": [
            {"name": "运营效率", "kpis": ["P_DELIVERY"]},
            {"name": "质量合格率", "kpis": ["P_PASS_RATE"]},
        ],
    },
    {
        "key": "learning",
        "name": "学习成长层",
        "icon": "📚",
        "color": "#E6A23C",
        "objectives": [
            {"name": "关键岗位胜任度", "kpis": ["L_COMPETENCY"]},
            {"name": "培训完成率", "kpis": ["L_TRAINING"]},
        ],
    },
]

# ── CRUD ────────────────────────────────────

@router.get("")
def list_maps(db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
    # 账套隔离: 仅当前企业地图 (2026-08-25)
    maps = db.query(StrategicMap).filter(StrategicMap.entity_id == entity_id).order_by(StrategicMap.updated_at.desc()).all()
    return {"data": [m_to_dict(m, db) for m in maps]}

@router.post("")
def create_map(data: dict, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
    # 空白地图默认四层空结构（避免dimensions=NULL导致加载报错）
    if "dimensions" not in data or data.get("dimensions") is None:
        data = {**data, "dimensions": [
            {"key": "finance", "name": "财务层", "icon": "💰", "color": "#F56C6C", "objectives": []},
            {"key": "customer", "name": "客户层", "icon": "👥", "color": "#409EFF", "objectives": []},
            {"key": "process", "name": "流程层", "icon": "⚙️", "color": "#67C23A", "objectives": []},
            {"key": "learning", "name": "学习层", "icon": "📚", "color": "#E6A23C", "objectives": []},
        ]}
    if "canvas_data" not in data or data.get("canvas_data") is None:
        data = {**data, "canvas_data": {"connections": []}}
    data["entity_id"] = entity_id  # 账套隔离: 强制token企业
    m = StrategicMap(**data)
    db.add(m)
    db.commit()
    db.refresh(m)
    _sync_map_objectives(m, db)
    return m_to_dict(m, db)


@router.post("/create-with-template")
def create_map_with_template(data: dict, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
    """一键创建带四维度模板的战略地图（账套隔离: 强制token企业）"""
    m = StrategicMap(
        title=data.get("title", "新建战略地图"),
        version=data.get("version", "v1.0"),
        status="draft",
        entity_id=entity_id,
        dimensions=STRATEGIC_MAP_TEMPLATE,
        canvas_data={"connections": []},
    )
    db.add(m)
    db.commit()
    db.refresh(m)
    _sync_map_objectives(m, db)
    return m_to_dict(m, db)


@router.put("/{map_id}")
def update_map(map_id: int, data: dict, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
    m = db.query(StrategicMap).filter(StrategicMap.id == map_id).first()
    if not m:
        raise HTTPException(404, "战略地图不存在")
    # 账套隔离: 禁止跨企业修改
    if m.entity_id != entity_id:
        raise HTTPException(404, "战略地图不存在")

    old_status = m.status
    # 安全白名单: 只允许编辑业务字段, 禁止 entity_id 等敏感字段被请求体篡改 (OpenCode审查#7)
    # status 允许但仅限合法值域(draft/published), 防止任意发布
    ALLOWED_MAP_FIELDS = {"title", "version", "dimensions", "canvas_data", "description", "status"}
    for k, v in data.items():
        if k in ALLOWED_MAP_FIELDS and hasattr(m, k) and v is not None:
            if k == "status" and v not in ("draft", "published"):
                continue
            setattr(m, k, v)

    db.commit()
    # 同步目标到map_objectives表
    _sync_map_objectives(m, db)
    # KR完整修复(2026-08-27): 同步 objectives+krs 表
    _sync_objectives_krs(m, db)

    # ├─ 版本管理: draft → published 时自动创建快照
    if old_status == "draft" and m.status == "published":
        _auto_snapshot(m, db)

    return m_to_dict(m, db)


# ── 删除地图 ─────────────────────────────────


@router.delete("/{map_id}")
def delete_map(map_id: int, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
    """删除战略地图（账套隔离: 禁止跨企业删除）"""
    m = db.query(StrategicMap).filter(StrategicMap.id == map_id).first()
    if not m:
        raise HTTPException(404, "战略地图不存在")
    if m.entity_id != entity_id:
        raise HTTPException(404, "战略地图不存在")
    db.delete(m)
    db.commit()
    return {"message": "已删除"}


@router.post("/batch-delete")
def batch_delete_maps(data: dict, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
    """批量删除战略地图（账套隔离: 仅本企业地图）"""
    ids = data.get("ids", [])
    if not ids:
        raise HTTPException(400, "请选择要删除的地图")
    deleted = 0
    for mid in ids:
        m = db.query(StrategicMap).filter(StrategicMap.id == mid, StrategicMap.entity_id == entity_id).first()
        if m:
            db.delete(m)
            deleted += 1
    db.commit()
    return {"message": f"已删除 {deleted} 个地图", "deleted": deleted}


# ── 连线管理 ─────────────────────────────────

def _get_connections(m: StrategicMap) -> list:
    if not m.canvas_data:
        m.canvas_data = {"connections": []}
    if isinstance(m.canvas_data, str):
        try:
            m.canvas_data = json.loads(m.canvas_data)
        except:
            m.canvas_data = {"connections": []}
    if "connections" not in m.canvas_data:
        m.canvas_data["connections"] = []
    return m.canvas_data["connections"]


@router.post("/{map_id}/connections")
def add_connection(map_id: int, data: dict, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
    """新增因果连线: {"from": "learning-0", "to": "process-0"}"""
    m = db.query(StrategicMap).filter(StrategicMap.id == map_id, StrategicMap.entity_id == entity_id).first()
    if not m:
        raise HTTPException(404, "战略地图不存在")

    from_id = data.get("from", "")
    to_id = data.get("to", "")

    if not from_id or not to_id:
        raise HTTPException(400, "请提供 from 和 to")

    # 校验: 不能自连
    if from_id == to_id:
        raise HTTPException(400, "不能自身连线")

    # 校验: 维度不能相同 (但放开允许同层连线, 仅禁止自连)
    from_dim = from_id.rsplit("-", 1)[0]
    to_dim = to_id.rsplit("-", 1)[0]

    conns = _get_connections(m)

    # 校验: 不能重复
    for c in conns:
        if c.get("from") == from_id and c.get("to") == to_id:
            raise HTTPException(400, "已存在相同的连线")

    conns.append({
        "from": from_id,
        "to": to_id,
        "style": "solid",
        "effect": data.get("effect", "positive"),
        "label": data.get("label", ""),
    })
    m.canvas_data["connections"] = conns
    db.commit()
    return {"connections": conns}


@router.delete("/{map_id}/connections")
def delete_connection_by_key(map_id: int, data: dict, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
    """根据 from/to 删除连线"""
    m = db.query(StrategicMap).filter(StrategicMap.id == map_id, StrategicMap.entity_id == entity_id).first()
    if not m:
        raise HTTPException(404, "战略地图不存在")

    from_id = data.get("from", "")
    to_id = data.get("to", "")

    conns = _get_connections(m)
    new_conns = [c for c in conns if not (c.get("from") == from_id and c.get("to") == to_id)]

    if len(new_conns) == len(conns):
        raise HTTPException(404, "连线不存在")

    m.canvas_data["connections"] = new_conns
    db.commit()
    return {"connections": new_conns, "removed": {"from": from_id, "to": to_id}}


# ── 版本管理 ─────────────────────────────────

def _auto_snapshot(m: StrategicMap, db: Session):
    """发布时自动创建版本快照"""
    from app.models import StrategicMapVersion
    import re

    # 自动递增版本号: 找到最大次版本号
    existing = db.query(StrategicMapVersion).filter(
        StrategicMapVersion.map_id == m.id
    ).order_by(StrategicMapVersion.id.desc()).first()

    if existing:
        match = re.search(r"v(\d+)\.(\d+)", existing.version)
        if match:
            major = int(match.group(1))
            minor = int(match.group(2)) + 1
            new_ver = f"v{major}.{minor}"
        else:
            new_ver = "v1.0"
    else:
        new_ver = "v1.0"

    # 确保 JSON 序列化
    dims = m.dimensions
    canvas = m.canvas_data
    if isinstance(dims, str):
        try:
            dims = json.loads(dims)
        except:
            dims = []
    if isinstance(canvas, str):
        try:
            canvas = json.loads(canvas)
        except:
            canvas = {"connections": []}

    snapshot = StrategicMapVersion(
        map_id=m.id,
        version=new_ver,
        dimensions=dims,
        canvas_data=canvas,
        comment=f"发布 {new_ver}",
    )
    db.add(snapshot)
    db.commit()


# ── 工具函数 ─────────────────────────────────

def m_to_dict(m, db: Session = None):
    d = {c.name: getattr(m, c.name) for c in m.__table__.columns}
    if db:
        _merge_map_objectives(m, db)
        d["dimensions"] = m.dimensions
    return d


def _sync_map_objectives(m, db):
    """保存时：将dimensions JSON中的目标同步到map_objectives表"""
    db.query(MapObjective).filter(MapObjective.map_id == m.id).delete()
    dims = m.dimensions
    if isinstance(dims, str):
        dims = json.loads(dims)
    dims = dims or []
    for dim in dims:
        for i, obj in enumerate(dim.get("objectives", [])):
            mo = MapObjective(
                map_id=m.id,
                entity_id=m.entity_id if m.entity_id is not None else 1,  # 强制继承地图企业(OpenCode审查#11)
                dimension_key=dim.get("key", ""),
                name=obj.get("name", ""),
                description=obj.get("description", ""),
                icon=obj.get("icon", "target"),
                kpis=obj.get("kpis", []),
                sort_order=i,
            )
            db.add(mo)
    db.commit()


def _merge_map_objectives(m, db):
    """读取时：将map_objectives表的数据合并进dimensions JSON"""
    objs = db.query(MapObjective).filter(MapObjective.map_id == m.id).order_by(MapObjective.sort_order).all()
    if not objs:
        return
    dims = m.dimensions
    if isinstance(dims, str):
        dims = json.loads(dims)
    dims = dims or []
    # 按dimension_key分组
    from collections import defaultdict
    grouped = defaultdict(list)
    for o in objs:
        grouped[o.dimension_key].append(o)
    for dim in dims:
        key = dim.get("key", "")
        if key in grouped:
            merged = []
            for i, o in enumerate(grouped[key]):
                # 保留原有JSON中的额外字段（targetValue/krs等）
                existing = dim["objectives"][i] if i < len(dim["objectives"]) else {}
                merged.append({
                    "name": o.name,
                    "description": o.description or existing.get("description", ""),
                    "icon": o.icon or existing.get("icon", "target"),
                    "kpis": o.kpis or existing.get("kpis", []),
                    **{k: existing[k] for k in existing if k not in ("name", "description", "icon", "kpis")},
                })
            dim["objectives"] = merged
    m.dimensions = dims


# ── KR完整修复(2026-08-27): 地图节点 → objectives + krs 表同步 ──

KR_OPERATORS = (">=", "<=", ">", "<", "=")


def _parse_kr_operator_target(raw_target, operator=None):
    """解析KR目标值字符串:
    旧格式 '≥20%'/'<=18' → ('>=', 20.0)
    新格式 operator='>=' + target=20 → ('>=', 20.0)
    """
    if operator:
        op = str(operator).strip()
        if op == "≥":
            op = ">="
        elif op == "≤":
            op = "<="
        if op not in KR_OPERATORS:
            op = ">="
    else:
        op = ">="
    if raw_target is None:
        return op, None
    if isinstance(raw_target, (int, float)):
        return op, float(raw_target)
    s = str(raw_target).strip()
    if s == "":
        return op, None
    # 前缀符号
    for cand in KR_OPERATORS:
        if s.startswith(cand):
            op = cand
            s = s[len(cand):]
            break
    if s.startswith("≥"):
        op = ">="
        s = s[1:]
    elif s.startswith("≤"):
        op = "<="
        s = s[1:]
    # 去掉单位（%、天、小时等非数字后缀）
    num = s
    while num and not (num[0].isdigit() or num[0] in ".+-"):
        num = num[1:]
    import re
    m = re.match(r"[-+]?\d*\.?\d+", num)
    if not m:
        return op, None
    return op, float(m.group(0))


def _sync_objectives_krs(m, db):
    """保存地图时：将dimensions JSON中带krs的目标节点同步到objectives表+krs表。
    - 每个带krs的目标节点 → 确保objectives表有记录（按entity_id+name匹配，无则创建）
    - krs数组 → krs表 upsert（按title匹配；删除本目标下JSON中不存在的krs）
    - 回写 objective_id / kr_id 到节点JSON，方便前端下次直接使用
    """
    dims = m.dimensions
    if isinstance(dims, str):
        try:
            dims = json.loads(dims)
        except Exception:
            return
    dims = dims or []
    entity_id = m.entity_id if m.entity_id is not None else 1
    for dim in dims:
        dim_key = dim.get("key", "")
        for obj in dim.get("objectives", []):
            krs = obj.get("krs") or []
            if not krs:
                continue
            name = (obj.get("name") or "").strip()
            if not name:
                continue
            # 1. 确保 Objective 记录存在
            objective = db.query(Objective).filter(
                Objective.entity_id == entity_id,
                Objective.title == name,
            ).first()
            if not objective:
                objective = Objective(
                    entity_id=entity_id,
                    title=name,
                    description=obj.get("description") or "",
                    dimension=dim_key,
                    quarter=_map_quarter(m),
                    owner=obj.get("owner") or "",
                    status="active",
                    progress=0,
                )
                db.add(objective)
                db.flush()  # 拿到 id
            obj["_objective_id"] = objective.id
            # 2. upsert krs 表
            existing_krs = {kr.title: kr for kr in db.query(KR).filter(KR.objective_id == objective.id).all()}
            keep_ids = set()
            for i, kr_item in enumerate(krs):
                if not isinstance(kr_item, dict):
                    continue
                title = (kr_item.get("title") or kr_item.get("name") or "").strip()
                if not title:
                    # 跳过空KR（前端允许空白行，不入库）
                    continue
                op, tgt = _parse_kr_operator_target(
                    kr_item.get("target_value"), kr_item.get("operator"))
                kpi_id = kr_item.get("metric_kpi_id")
                if not kpi_id and kr_item.get("kpi_code"):
                    kpi = db.query(KPIDefinition).filter(
                        KPIDefinition.entity_id == entity_id,
                        KPIDefinition.kpi_code == kr_item["kpi_code"],
                    ).first()
                    kpi_id = kpi.id if kpi else None
                # 关联KPI时自动继承方向（KPI阈值如 '>=800'）
                if kpi_id and not kr_item.get("operator"):
                    kpi = db.query(KPIDefinition).filter(KPIDefinition.id == kpi_id).first()
                    if kpi and kpi.threshold_green:
                        op2, tgt2 = _parse_kr_operator_target(kpi.threshold_green)
                        op, tgt = op2, (tgt2 if tgt is None else tgt)
                kr = existing_krs.get(title)
                if kr is None:
                    kr = KR(
                        entity_id=entity_id,
                        objective_id=objective.id,
                        title=title,
                    )
                    db.add(kr)
                kr.metric_kpi_id = kpi_id
                kr.operator = op
                if kr_item.get("tolerance") is not None:
                    kr.tolerance = kr_item.get("tolerance")
                if kr_item.get("weight") is not None:
                    kr.weight = kr_item.get("weight")
                if tgt is not None:
                    kr.target_value = tgt
                if kr_item.get("current_value") is not None:
                    kr.current_value = kr_item.get("current_value")
                if kr_item.get("due_date"):
                    kr.due_date = _parse_kr_date(kr_item.get("due_date"))
                kr.sort_order = i + 1
                keep_ids.add(kr.id)
                kr_item["_kr_id"] = kr.id
            # 3. 删除JSON中不存在的krs（空标题的除外——它从未入库）
            for title, kr in existing_krs.items():
                if kr.id not in keep_ids:
                    db.delete(kr)
    db.commit()
    # 回写dimensions（含_objective_id/_kr_id）
    m.dimensions = dims
    db.commit()


def _map_quarter(m) -> str:
    """从地图标题提取季度标识（如 2026Q3），无则空字符串"""
    title = m.title or ""
    import re
    m_q = re.search(r"(20\d{2})\s*[Qq](\d)", title)
    if m_q:
        return f"{m_q.group(1)}Q{m_q.group(2)}"
    return ""


def _parse_kr_date(v):
    if v is None or v == "":
        return None
    if isinstance(v, datetime):
        return v.date()
    try:
        return datetime.strptime(str(v)[:10], "%Y-%m-%d").date()
    except (ValueError, TypeError):
        return None


# ── 战略回顾会 聚合接口 ──────────────────────


@router.get("/{map_id}/krs")
def get_map_krs(map_id: int, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
    """战略回顾会 KR进度: 从krs表读取地图各目标的KR (KR修复 2026-08-27)

    地图dimensions JSON中每个带krs的目标节点已同步到objectives+krs表，
    此处按 节点._objective_id（或 entity+name 回查）取KR。
    """
    from app.models import KPIDefinition, KR, Objective
    m = db.query(StrategicMap).filter(StrategicMap.id == map_id, StrategicMap.entity_id == entity_id).first()
    if not m:
        raise HTTPException(404, "战略地图不存在")

    dims = m.dimensions
    if isinstance(dims, str):
        try:
            dims = json.loads(dims)
        except Exception:
            dims = []
    dims = dims or []

    obj_ids = []
    for dim in dims:
        for obj in dim.get("objectives", []):
            oid = obj.get("_objective_id")
            if oid:
                obj_ids.append(int(oid))
                continue
            # 兼容无回写字段的旧节点: 按 entity+name 回查
            name = (obj.get("name") or "").strip()
            if name:
                o = db.query(Objective).filter(
                    Objective.entity_id == entity_id,
                    Objective.title == name,
                ).first()
                if o:
                    obj_ids.append(o.id)

    result = []
    if obj_ids:
        krs = db.query(KR).filter(
            KR.entity_id == entity_id,
            KR.objective_id.in_(obj_ids),
        ).order_by(KR.objective_id, KR.sort_order, KR.id).all()
        for kr in krs:
            mkpi = None
            if kr.metric_kpi_id:
                mkpi = db.query(KPIDefinition).filter(KPIDefinition.id == kr.metric_kpi_id).first()
            result.append({
                "id": kr.id,
                "objective_id": kr.objective_id,
                "title": kr.title,
                "metric_kpi_id": kr.metric_kpi_id,
                "metric_kpi_code": mkpi.kpi_code if mkpi else None,
                "metric_kpi_name": mkpi.kpi_name if mkpi else None,
                "operator": _parse_kr_operator_target(kr.operator)[0] if hasattr(kr, "operator") and kr.operator else ">=",
                "weight": float(kr.weight) if kr.weight is not None else None,
                "target_value": float(kr.target_value) if kr.target_value is not None else None,
                "current_value": float(kr.current_value) if kr.current_value is not None else None,
                "progress": kr.progress or 0,
                "status": kr.status,
                "due_date": kr.due_date.isoformat() if kr.due_date else None,
            })
    return {"data": result}


@router.get("/{map_id}/review")
def get_map_review(map_id: int, level: Optional[str] = None, db: Session = Depends(get_db), entity_id: int = Depends(get_entity_id)):
    """战略回顾会：返回目标状态、KPI值、改善行动

    level: 可选 strategic/operational — 战略回顾(默认strategic)只显示战略级KPI；
           不传则返回全部KPI（向后兼容）。
    """
    from app.models import KPIDefinition, KPIValue, ActionPlan
    m = db.query(StrategicMap).filter(StrategicMap.id == map_id, StrategicMap.entity_id == entity_id).first()
    if not m:
        raise HTTPException(404, "战略地图不存在")

    dims = m.dimensions
    if isinstance(dims, str):
        dims = json.loads(dims)
    dims = dims or []  # NULL容错

    # 收集所有KPI code
    all_kpi_codes = set()
    for dim in dims:
        for obj in dim.get("objectives", []):
            for code in obj.get("kpis", []):
                all_kpi_codes.add(code)

    # 查询KPI定义（账套隔离: 同编码跨企业会混入, OpenCode审查#10）
    kpi_defs = db.query(KPIDefinition).filter(
        KPIDefinition.kpi_code.in_(all_kpi_codes) if all_kpi_codes else False,
        KPIDefinition.entity_id == entity_id,
    ).all() if all_kpi_codes else []
    kpi_map = {k.kpi_code: k for k in kpi_defs}

    # 查询最新KPI实际值
    kpi_ids = [k.id for k in kpi_defs]
    latest_values = {}
    if kpi_ids:
        # 取每个KPI的最新一条
        for kid in kpi_ids:
            v = db.query(KPIValue).filter(
                KPIValue.kpi_id == kid
            ).order_by(KPIValue.calculated_at.desc()).first()
            if v:
                latest_values[kid] = {
                    "actual_value": v.actual_value,
                    "period": v.period,
                    "source_type": v.source_type,
                }

    # 查询改善行动（按KPI_id关联）
    action_plans_data = []
    if kpi_ids:
        plans = db.query(ActionPlan).filter(
            ActionPlan.kpi_id.in_(kpi_ids)
        ).order_by(ActionPlan.created_at.desc()).all()
        for p in plans:
            action_plans_data.append({
                "id": p.id,
                "kpi_id": p.kpi_id,
                "title": p.title,
                "assignee": p.assignee,
                "priority": p.priority,
                "due_date": p.due_date.isoformat() if p.due_date else None,
                "status": p.status,
                "progress": p.progress or 0,
                "created_at": p.created_at.isoformat() if p.created_at else None,
            })

    # 构建维度目标状态
    dim_results = []
    total_ok = 0
    total_warn = 0
    total_err = 0
    total_obj_count = 0
    focus_items = []

    for dim in dims:
        dim_key = dim.get("key", "")
        dim_name = dim.get("name", "")
        dim_icon = dim.get("icon", "")
        dim_color = dim.get("color", "")
        objectives = []
        for obj in dim.get("objectives", []):
            total_obj_count += 1
            obj_kpis = []
            worst_level = "green"
            for code in obj.get("kpis", []):
                kpi_def = kpi_map.get(code)
                if not kpi_def:
                    continue
                # 规则4: 战略地图默认只显示strategic级KPI（level过滤）
                if level and kpi_def.kpi_level != level:
                    continue
                lv = latest_values.get(kpi_def.id, {})
                actual = lv.get("actual_value")
                # 多粒度目标优先: 月目标(多数KPI月度) → 兼容legacy target_value (OpenCode审查#5)
                target = kpi_def.target_monthly or kpi_def.target_value
                # 判断红黄绿灯（绿≥90% / 黄60-90% / 红<60%）
                # 方向修正: 越低越好型(费用率/成本率/损耗/逾期/投诉/流失) 与 负值目标 (OpenCode审查#5)
                level = "gray"
                if actual is not None and target:
                    name = (kpi_def.kpi_name or "") + " " + (kpi_def.kpi_code or "")
                    lower_better = any(k in name for k in
                                       ("费用率", "成本率", "损耗", "逾期", "投诉", "流失", "负债率", "返修", "缺勤"))
                    if lower_better:
                        ratio = target / actual if actual else 0  # 实际越低越好: 目标/实际
                    else:
                        ratio = actual / target
                    if ratio < 0:
                        ratio = 0  # 负值目标(如亏损控制) 完成度不为负
                    if ratio >= 0.9:
                        level = "green"
                    elif ratio >= 0.6:
                        level = "yellow"
                    else:
                        level = "red"
                else:
                    level = "gray"

                if level == "red":
                    worst_level = "red"
                elif level == "yellow" and worst_level != "red":
                    worst_level = "yellow"

                obj_kpis.append({
                    "kpi_id": kpi_def.id,
                    "kpi_code": code,
                    "kpi_name": kpi_def.kpi_name,
                    "target_value": target,
                    "actual_value": actual,
                    "unit": kpi_def.unit,
                    "level": level,
                })

            obj_item = {
                "name": obj.get("name", ""),
                "icon": obj.get("icon", ""),
                "kpis": obj_kpis,
                "level": worst_level,
                "has_data": len(obj_kpis) > 0,
                # 行动方案汇总(OpenCode审查#6): 按本目标KPI统计
                "action_plan_summary": {
                    "total": len([p for p in action_plans_data if p["kpi_id"] in [k["kpi_id"] for k in obj_kpis]]),
                    "pending": len([p for p in action_plans_data
                                    if p["kpi_id"] in [k["kpi_id"] for k in obj_kpis]
                                    and p["status"] not in ("done", "completed")]),
                } if obj_kpis else {"total": 0, "pending": 0},
            }
            objectives.append(obj_item)

            if worst_level == "green":
                total_ok += 1
            elif worst_level == "yellow":
                total_warn += 1
            elif worst_level == "red":
                total_err += 1

            # 红色和黄色归入需重点关注
            if worst_level in ("red", "yellow"):
                focus_items.append(obj_item)

        dim_results.append({
            "key": dim_key,
            "name": dim_name,
            "icon": dim_icon,
            "color": dim_color,
            "objectives": objectives,
        })

    # 排序：红色在前，黄色在后
    focus_items.sort(key=lambda x: (0 if x["level"] == "red" else 1, x["name"]))

    return {
        "map_id": m.id,
        "title": m.title,
        "version": m.version,
        "status": m.status,
        "dimensions": dim_results,
        "summary": {
            "total": total_obj_count,
            "green": total_ok,
            "yellow": total_warn,
            "red": total_err,
            "health_score": round(total_ok / total_obj_count * 100, 1) if total_obj_count > 0 else 0,
        },
        "focus_items": focus_items,
        "action_plans": action_plans_data,
    }


# ── 复盘记录持久化（大PDCA Act闭环 2026-08-27）────────────────
def review_to_dict(r: ReviewRecord) -> dict:
    return {
        "id": r.id,
        "entity_id": r.entity_id,
        "map_id": r.map_id,
        "review_type": r.review_type,
        "review_date": r.review_date.isoformat() if r.review_date else None,
        "summary": r.summary,
        "adjustments": r.adjustments,
        "next_priorities": r.next_priorities,
        "created_by": r.created_by,
        "created_at": r.created_at.isoformat() if r.created_at else None,
    }


def _parse_review_date(raw) -> Optional[date]:
    """兼容 'YYYY-MM-DD' / datetime / date 输入"""
    if raw is None:
        return datetime.now().date()
    if isinstance(raw, datetime):
        return raw.date()
    if isinstance(raw, date):
        return raw
    try:
        return datetime.strptime(str(raw)[:10], "%Y-%m-%d").date()
    except ValueError:
        return datetime.now().date()


def _get_map_or_404(db: Session, map_id: int, entity_id: int) -> StrategicMap:
    m = db.query(StrategicMap).filter(
        StrategicMap.id == map_id, StrategicMap.entity_id == entity_id
    ).first()
    if not m:
        raise HTTPException(404, "战略地图不存在")
    return m


@router.post("/{map_id}/review-records")
def create_review_record(
    map_id: int,
    data: dict,
    db: Session = Depends(get_db),
    entity_id: int = Depends(get_entity_id),
    current_user: User = Depends(require_auth),
):
    """保存复盘结论（战略回顾会/运营复盘会结束时落库）"""
    _get_map_or_404(db, map_id, entity_id)
    summary = (data.get("summary") or "").strip()
    if not summary:
        raise HTTPException(400, "复盘结论(summary)不能为空")

    r = ReviewRecord(
        entity_id=entity_id,  # 多租户隔离: 强制token企业
        map_id=map_id,
        review_type=data.get("review_type", "quarterly"),
        review_date=_parse_review_date(data.get("review_date")),
        summary=summary,
        adjustments=data.get("adjustments") or [],
        next_priorities=data.get("next_priorities") or [],
        created_by=current_user.id if current_user else None,
    )
    db.add(r)
    db.commit()
    db.refresh(r)
    return review_to_dict(r)


@router.get("/{map_id}/review-records")
def list_review_records(
    map_id: int,
    review_type: Optional[str] = None,
    db: Session = Depends(get_db),
    entity_id: int = Depends(get_entity_id),
):
    """历史复盘记录列表（按复盘日期倒序）"""
    _get_map_or_404(db, map_id, entity_id)
    q = db.query(ReviewRecord).filter(
        ReviewRecord.map_id == map_id,
        ReviewRecord.entity_id == entity_id,
    )
    if review_type:
        q = q.filter(ReviewRecord.review_type == review_type)
    records = q.order_by(ReviewRecord.review_date.desc(), ReviewRecord.id.desc()).all()
    return {"data": [review_to_dict(r) for r in records]}


@router.get("/{map_id}/review-records/{record_id}")
def get_review_record(
    map_id: int,
    record_id: int,
    db: Session = Depends(get_db),
    entity_id: int = Depends(get_entity_id),
):
    """单条复盘记录详情"""
    _get_map_or_404(db, map_id, entity_id)
    r = db.query(ReviewRecord).filter(
        ReviewRecord.id == record_id,
        ReviewRecord.map_id == map_id,
        ReviewRecord.entity_id == entity_id,
    ).first()
    if not r:
        raise HTTPException(404, "复盘记录不存在")
    return review_to_dict(r)

