"""差异分析→战略地图反打 API — P1-1

允许从差异分析页面一键回写实际值到战略地图节点，触发预警并生成回顾会议题。
"""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from app.database import get_db
from app.auth_middleware import require_auth, require_role
from app.models import StrategicMap, KPIDefinition, KPIValue, KPIAlert, OperationLog, ActionPlan, BudgetPlan
import json
import logging
from datetime import datetime

logger = logging.getLogger("cma.deviation_push")

router = APIRouter(prefix="/api/cma/deviation-push", tags=["差异反打"],
    dependencies=[Depends(require_role("ceo", "finance", "business"))],
)


@router.post("/push-to-map")
def push_deviation_to_map(
    data: dict,
    db: Session = Depends(get_db),
    current_user=Depends(require_auth),
):
    """从差异分析回写实际值到战略地图节点

    Body: {
        mapId: int,
        nodeId: string,       // 格式 "dim_key-index" 如 "finance-0"
        deviationId: int,
        newValue: float,
        period: string,       // 如 "2026-05"
        createReviewTopic: bool
    }
    """
    map_id = data.get("mapId")
    node_id = data.get("nodeId")
    deviation_id = data.get("deviationId")
    new_value = data.get("newValue")
    period = data.get("period")
    create_review_topic = data.get("createReviewTopic", True)

    if not map_id or not node_id:
        raise HTTPException(400, "缺少 mapId 或 nodeId")
    if new_value is None:
        raise HTTPException(400, "缺少 newValue")

    # 1. 查找战略地图
    m = db.query(StrategicMap).filter(StrategicMap.id == map_id).first()
    if not m:
        raise HTTPException(404, "战略地图不存在")

    # 2. 解析 node_id 格式: "finance-0"
    dims = m.dimensions
    if isinstance(dims, str):
        try:
            dims = json.loads(dims)
        except:
            dims = []

    parts = node_id.rsplit("-", 1)
    if len(parts) != 2:
        raise HTTPException(400, f"节点ID格式错误: {node_id}")

    dim_key, obj_index_str = parts
    try:
        obj_index = int(obj_index_str)
    except ValueError:
        raise HTTPException(400, f"节点索引不是数字: {obj_index_str}")

    target_dim = None
    target_obj = None
    for dim in dims:
        if dim.get("key") == dim_key:
            target_dim = dim
            objs = dim.get("objectives", [])
            if 0 <= obj_index < len(objs):
                target_obj = objs[obj_index]
            break

    if not target_obj:
        raise HTTPException(404, f"未找到节点: {node_id}")

    kpi_codes = target_obj.get("kpis", [])
    if not kpi_codes:
        raise HTTPException(400, f"目标 [{target_obj.get('name')}] 没有关联KPI")

    kpi_code = kpi_codes[0]
    kpi = db.query(KPIDefinition).filter(KPIDefinition.kpi_code == kpi_code).first()
    if not kpi:
        raise HTTPException(404, f"KPI {kpi_code} 不存在")

    # 3. 更新实际值到 KPIValue 表
    if not period:
        period = datetime.now().strftime("%Y-%m")

    existing_value = db.query(KPIValue).filter(
        KPIValue.kpi_id == kpi.id,
        KPIValue.period == period,
    ).first()

    if existing_value:
        existing_value.actual_value = new_value
        existing_value.source_type = "manual"
    else:
        kv = KPIValue(
            kpi_id=kpi.id,
            period=period,
            actual_value=new_value,
            source_type="manual",
        )
        db.add(kv)

    db.flush()

    # 4. 检查是否触发预警
    alert_created = False
    alert_id = None
    if kpi.target_value and kpi.target_value > 0:
        ratio = new_value / kpi.target_value
        if ratio < 0.7:
            alert_level = "red"
            alert_msg = f"严重偏差: {kpi.kpi_name}实际值{new_value}，目标值{kpi.target_value}，达成率{ratio*100:.1f}%"
        elif ratio < 0.9:
            alert_level = "yellow"
            alert_msg = f"关注偏差: {kpi.kpi_name}实际值{new_value}，目标值{kpi.target_value}，达成率{ratio*100:.1f}%"
        else:
            alert_level = None

        if alert_level:
            alert = KPIAlert(
                kpi_id=kpi.id,
                alert_level=alert_level,
                alert_message=alert_msg,
                status="pending",
            )
            db.add(alert)
            db.flush()
            alert_created = True
            alert_id = alert.id

    # 5. 生成战略回顾会议题
    review_topic_created = False
    if create_review_topic:
        topic_title = f"【差异反打】{kpi.kpi_name}偏差回写 — {target_obj.get('name')}"
        existing_topic = db.query(ActionPlan).filter(
            ActionPlan.title == topic_title,
            ActionPlan.status.in_(["pending", "in_progress"]),
        ).first()
        if not existing_topic:
            topic = ActionPlan(
                kpi_id=kpi.id,
                title=topic_title,
                description=f"由差异分析自动生成：将实际值{new_value}回写至战略地图[{target_dim.get('name')}→{target_obj.get('name')}]节点。差异ID: {deviation_id or 'N/A'}",
                assignee=current_user.name if hasattr(current_user, "name") else "",
                priority="medium",
                status="pending",
                created_by=current_user.name if hasattr(current_user, "name") else "",
            )
            db.add(topic)
            review_topic_created = True

    # 6. 操作日志
    log = OperationLog(
        user_id=getattr(current_user, "id", None),
        action="deviation_push_to_map",
        target_type="map",
        target_id=map_id,
        detail=json.dumps({
            "node_id": node_id,
            "deviation_id": deviation_id,
            "kpi_code": kpi_code,
            "new_value": new_value,
            "period": period,
            "alert_created": alert_created,
            "review_topic_created": review_topic_created,
        }, ensure_ascii=False),
    )
    db.add(log)
    db.commit()

    return {
        "success": True,
        "message": f"已回写至战略地图 [{target_dim.get('name')}→{target_obj.get('name')}]",
        "kpi_code": kpi_code,
        "kpi_name": kpi.kpi_name,
        "new_value": new_value,
        "alert_created": alert_created,
        "alert_id": alert_id,
        "review_topic_created": review_topic_created,
    }


@router.get("/map-nodes/{map_id}")
def get_map_nodes(map_id: int, db: Session = Depends(get_db)):
    """获取战略地图的全部节点（供反打选择使用）"""
    m = db.query(StrategicMap).filter(StrategicMap.id == map_id).first()
    if not m:
        raise HTTPException(404, "战略地图不存在")

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

    nodes = []
    for dim in dims:
        objs = dim.get("objectives", [])
        for idx, obj in enumerate(objs):
            node_id = f"{dim.get('key')}-{idx}"
            nodes.append({
                "node_id": node_id,
                "dim_key": dim.get("key"),
                "dim_name": dim.get("name"),
                "dim_icon": dim.get("icon"),
                "objective_name": obj.get("name"),
                "kpi_codes": obj.get("kpis", []),
            })

    return {"data": nodes}
