"""现金流预测引擎 — 根据历史KPI数据推算未来30天现金流"""
import logging
from datetime import datetime, timedelta
from typing import Optional
from sqlalchemy.orm import Session
import math
import random

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

# 默认现金阈值（万元）
DEFAULT_CASH_WARNING = 20.0   # 黄灯 — 低于20万
DEFAULT_CASH_CRITICAL = 10.0  # 红灯 — 低于10万

# 历史KPI编码映射
KPI_CODES = {
    "operating_cash_flow": "CASH_FLOW_001",   # 经营现金流
    "receivables": "AR_001",                    # 应收账款
    "payables": "AP_001",                      # 应付账款
    "cash_balance": "CASH_001",                # 现金余额
}


def get_entity_kpi_history(entity_id: int, kpi_code: str, db: Session, limit_months: int = 6) -> list:
    """获取实体某个KPI的历史值"""
    from app.models import KPIDefinition, KPIValue
    kpi = db.query(KPIDefinition).filter(
        KPIDefinition.kpi_code == kpi_code,
        KPIDefinition.entity_id == entity_id,
    ).first()
    if not kpi:
        return []
    values = db.query(KPIValue).filter(
        KPIValue.kpi_id == kpi.id,
        KPIValue.actual_value.isnot(None),
    ).order_by(KPIValue.period.desc()).limit(limit_months).all()
    return values


def calc_trend(values: list) -> float:
    """计算趋势系数 — 线性回归斜率 / 均值"""
    if len(values) < 2:
        return 0.0
    vals = [v.actual_value for v in values]
    n = len(vals)
    avg_x = (n - 1) / 2.0
    avg_y = sum(vals) / n
    num = sum((i - avg_x) * (vals[i] - avg_y) for i in range(n))
    den = sum((i - avg_x) ** 2 for i in range(n))
    slope = num / den if den != 0 else 0
    return slope / max(abs(avg_y), 1.0) * 100  # 趋势百分比


def forecast_cash_flow(
    entity_id: int,
    db: Session,
    days: int = 30,
    current_cash: Optional[float] = None,
) -> dict:
    """
    预测未来30天现金流
    
    算法：
    1. 获取历史经营现金流、应收、应付趋势
    2. 推算每日现金流入/流出
    3. 生成每日预测值+置信区间
    """
    from app.models import KPIDefinition, KPIValue

    # 获取当前现金余额
    if current_cash is None:
        cash_kpi = db.query(KPIDefinition).filter(
            KPIDefinition.kpi_code == KPI_CODES["cash_balance"],
            KPIDefinition.entity_id == entity_id,
        ).first()
        if cash_kpi:
            latest_cash = db.query(KPIValue).filter(
                KPIValue.kpi_id == cash_kpi.id,
                KPIValue.actual_value.isnot(None),
            ).order_by(KPIValue.period.desc()).first()
            base_cash = latest_cash.actual_value if latest_cash else 30.0
        else:
            base_cash = 30.0  # 默认假设30万
    else:
        base_cash = current_cash

    # 获取经营现金流历史
    ocf_history = get_entity_kpi_history(entity_id, KPI_CODES["operating_cash_flow"], db)
    ocf_trend = calc_trend(ocf_history)

    # 获取应收历史
    ar_history = get_entity_kpi_history(entity_id, KPI_CODES["receivables"], db)
    ar_trend = calc_trend(ar_history)

    # 获取应付历史
    ap_history = get_entity_kpi_history(entity_id, KPI_CODES["payables"], db)
    ap_trend = calc_trend(ap_history)

    # 计算日均现金变化
    ocf_avg = sum(v.actual_value for v in ocf_history) / max(len(ocf_history), 1) / 30.0 if ocf_history else 0.5

    # 预测逻辑：趋势影响 + 季节性（月底回款高峰）
    forecast = []
    cash = base_cash
    today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)

    for day_offset in range(1, days + 1):
        d = today + timedelta(days=day_offset)
        day_of_month = d.day
        is_month_end = day_of_month >= 25

        # 每日现金变化 = 经营现金流日均值 × (1 + 趋势调整) + 季节因子
        trend_factor = 1.0 + ocf_trend / 100.0
        daily_change = ocf_avg * trend_factor

        # 月底回款高峰
        if is_month_end:
            daily_change += ocf_avg * 0.3  # 月底多30%回款

        # 周末效应
        if d.weekday() >= 5:
            daily_change *= 0.5  # 周末收支减半

        cash += daily_change

        # 置信区间：随时间增加而扩大
        confidence_band = 1.0 + day_offset * 0.08  # 每过1天，区间扩大8%
        std = max(abs(daily_change) * confidence_band, 0.5)
        lower = cash - std * 0.5
        upper = cash + std * 0.5

        # 预警状态
        if cash < DEFAULT_CASH_CRITICAL:
            status = "red"
        elif cash < DEFAULT_CASH_WARNING:
            status = "yellow"
        else:
            status = "green"

        forecast.append({
            "date": d.strftime("%Y-%m-%d"),
            "day_offset": day_offset,
            "predicted_cash": round(cash, 2),
            "lower_bound": round(max(lower, 0), 2),
            "upper_bound": round(upper, 2),
            "alert_status": status,
        })

    # 整体预警
    min_cash = min(f["predicted_cash"] for f in forecast)
    min_date = next(f["date"] for f in forecast if f["predicted_cash"] == min_cash)

    suggestions = []
    if min_cash < DEFAULT_CASH_CRITICAL:
        suggestions.append({
            "type": "critical",
            "message": f"预计{min_date}现金余额降至{min_cash:.1f}万，低于警戒线{DEFAULT_CASH_CRITICAL}万",
            "actions": [
                "立即催收大额应收账款",
                "暂停非必要支出",
                "准备短期融资安排",
            ]
        })
    elif min_cash < DEFAULT_CASH_WARNING:
        suggestions.append({
            "type": "warning",
            "message": f"预计{min_date}现金余额降至{min_cash:.1f}万，低于关注线{DEFAULT_CASH_WARNING}万",
            "actions": [
                "加快应收账款回款",
                "控制采购付款节奏",
                "评估短期现金流压力",
            ]
        })

    return {
        "entity_id": entity_id,
        "base_cash": round(base_cash, 2),
        "days": days,
        "forecast": forecast,
        "min_cash": round(min_cash, 2),
        "min_cash_date": min_date,
        "trends": {
            "operating_cash_flow_trend_pct": round(ocf_trend, 2),
            "receivables_trend_pct": round(ar_trend, 2),
            "payables_trend_pct": round(ap_trend, 2),
        },
        "suggestions": suggestions,
    }


def save_forecast_to_db(entity_id: int, forecast_data: dict, db: Session):
    """将预测结果保存到数据库"""
    from app.models import CashForecast
    for f in forecast_data["forecast"]:
        forecast_date = datetime.strptime(f["date"], "%Y-%m-%d")
        cf = CashForecast(
            entity_id=entity_id,
            forecast_date=forecast_date,
            predicted_cash=f["predicted_cash"],
            lower_bound=f["lower_bound"],
            upper_bound=f["upper_bound"],
            alert_status=f["alert_status"],
        )
        db.add(cf)
    db.commit()


def calculate_accuracy(entity_id: int, db: Session) -> list:
    """计算预测准确率 — 对比上期预测 vs 本期实际"""
    from app.models import CashForecast, KPIDefinition, KPIValue

    # 获取实体最近的预测
    forecasts = db.query(CashForecast).filter(
        CashForecast.entity_id == entity_id,
    ).order_by(CashForecast.forecast_date.desc()).limit(90).all()

    # 获取实际的现金余额KPI值
    cash_kpi = db.query(KPIDefinition).filter(
        KPIDefinition.kpi_code == KPI_CODES["cash_balance"],
        KPIDefinition.entity_id == entity_id,
    ).first()

    if not cash_kpi or not forecasts:
        return []

    actuals = db.query(KPIValue).filter(
        KPIValue.kpi_id == cash_kpi.id,
        KPIValue.actual_value.isnot(None),
    ).order_by(KPIValue.period.desc()).limit(12).all()

    actual_map = {}
    for a in actuals:
        try:
            # period like "2026-07" -> month approx
            actual_map[a.period] = a.actual_value
        except:
            pass

    # 按月汇总预测值和实际值，计算准确率
    from collections import defaultdict
    monthly_forecast = defaultdict(list)
    for f in forecasts:
        month_key = f.forecast_date.strftime("%Y-%m")
        monthly_forecast[month_key].append(f.predicted_cash)

    results = []
    for month, f_vals in sorted(monthly_forecast.items()):
        if month in actual_map:
            f_avg = sum(f_vals) / len(f_vals)
            a_val = actual_map[month]
            mae = abs(f_avg - a_val)
            mape = abs((f_avg - a_val) / max(abs(a_val), 1)) * 100
            results.append({
                "period": month,
                "forecast_value": round(f_avg, 2),
                "actual_value": round(a_val, 2),
                "mae": round(mae, 2),
                "mape": round(mape, 2),
            })

    return results


def generate_scenario_suggestion(alert_type: str, kpi_name: str, extra: dict = None) -> dict:
    """根据预警类型生成情景建议"""
    suggestions = {
        "cash_low": {
            "title": "现金流紧张缓解方案",
            "description": f"现金余额低于阈值，建议加快应收账款催收、控制支出、评估短期融资。",
            "actions": [
                f"催收大额应收账款（预计回款{extra.get('expected_receivables', '待定')}万元）",
                "暂停非紧急采购和资本性支出",
                "与供应商协商延长账期",
                "评估银行短期授信额度",
            ],
            "priority": "high",
        },
        "cash_critical": {
            "title": "现金流危机应对方案",
            "description": f"现金余额接近断流，需立即采取紧急措施。",
            "actions": [
                "立即催收所有到期应收账款",
                "暂停所有非必要支出",
                "紧急联系银行安排短期贷款",
                "评估资产变现可能性",
            ],
            "priority": "high",
        },
        "cost_high": {
            "title": "成本管控优化方案",
            "description": f"成本率异常偏高，建议进行成本结构分析和优化。",
            "actions": [
                "逐项分析成本构成，识别异常项",
                "与供应商重新谈判采购价格",
                "评估流程优化降本空间",
                "建立费用审批红线上限",
            ],
            "priority": "medium",
        },
        "revenue_drop": {
            "title": "收入下滑应对方案",
            "description": f"收入出现下滑趋势，建议分析原因并制定恢复计划。",
            "actions": [
                "分析收入下滑原因（客户流失/价格战/需求变化）",
                "制定客户留存和挽回计划",
                "评估新产品/新市场机会",
                "优化销售激励政策",
            ],
            "priority": "high",
        },
    }

    sug = suggestions.get(alert_type, {
        "title": "改善建议",
        "description": "根据预警情况制定改善措施。",
        "actions": ["分析预警原因", "制定改善计划", "跟踪执行效果"],
        "priority": "medium",
    })

    if extra:
        sug["extra"] = extra
    return sug
