"""
ERP数据同步脚本 — 管理会计OS
根据 kpi_definitions.formula 中的规则从ERP系统拉取数据并写入 kpi_values
支持:
  - HTTP API 模式: 通过 erp-api-gateway 查询实时数据
  - Fallback 模式: API不可达时使用本地已有数据或标记待同步
  - 定时执行(crontab) + 手动触发
"""

import sys
import os
import json
import logging
import re
import urllib.request
import urllib.error
from datetime import datetime, timedelta

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from dotenv import load_dotenv
load_dotenv()

from app.database import get_engine, get_session_local
from app.models import KPIDefinition, KPIValue, OperationLog, DataSourceConfig

logger = logging.getLogger("erp_sync")

# ERP API 配置
ERP_API_BASE = os.getenv("ERP_API_BASE", "http://127.0.0.1:8300/api/v1")
ERP_API_KEY = os.getenv("ERP_API_KEY", "erp-gateway-key-bhwl-2026")

# 无DB会话时使用的静态映射（保底，使用真实KPI编码）
# 对应 data_source_config 表的 active 端点（id=1,2,3,4）
# {period_year} 和 {period_month} 在 _resolve_endpoint 中替换
STATIC_API_MAP = {
    "F_REVENUE": f"{ERP_API_BASE}/stats/monthly?year={{{'period_year'}}}",
    "F_GROSS_MARGIN": f"{ERP_API_BASE}/stats/gross-profit?year={{{'period_year'}}}&month={{{'period_month'}}}",
    "F_NET_PROFIT": f"{ERP_API_BASE}/stats/monthly?year={{{'period_year'}}}",
    "F_COST_RATIO": f"{ERP_API_BASE}/stats/monthly-cost?year={{{'period_year'}}}",
}

# ============================================================
# 公式解析
# ============================================================

def parse_formula(formula: str) -> dict:
    """解析KPI公式，提取ERP表和字段映射"""
    result = {"table": "MasterBill", "field": None, "agg": "SUM",
              "where": None, "raw": formula, "erp_direct": True}

    # 特殊处理中文描述的公式
    ZH_PATTERNS = {
        "前5客户销售额/总销售额*100": ("TOP5_CUSTOMER", "MasterBill"),
        "前5客户集中度": ("TOP5_CUSTOMER", "MasterBill"),
        "满意客户数/总客户数*100": ("CUSTOMER_SAT_RATIO", "MasterBill"),
        "准时交付订单/总订单*100": ("DELIVERY_RATE", "MasterBill"),
        "完成培训人数/应培训人数*100": ("TRAINING_RATE", "MasterBill"),
    }
    for zh_pattern, (agg_type, table) in ZH_PATTERNS.items():
        if zh_pattern in formula:
            result.update({"agg": agg_type, "table": table, "field_expr": formula,
                           "erp_direct": False})  # 不能直接跑SQL
            return result

    # 优先检测比率型公式: SUM(A)/SUM(B)*100
    ratio_m = re.match(r"(SUM|COUNT|AVG)\s*\((.+?)\)\s*/\s*(SUM|COUNT|AVG)\s*\((.+?)\)", formula, re.I)
    if ratio_m:
        result["agg"] = f"RATIO_{ratio_m.group(1)}"
        result["field_expr"] = f"({ratio_m.group(2)})/({ratio_m.group(4)})"
        return result

    # 匹配完整聚合: SUM(...), COUNT(DISTINCT ...), COUNT(...), AVG(...)
    m = re.match(r"(SUM|COUNT(?:\s+DISTINCT)?|AVG|MAX|MIN)\s*\((.+?)\)", formula, re.I)
    if not m:
        result["field_expr"] = "1"
        result["table"] = "MasterBill"
        result["erp_direct"] = False
        return result

    agg_func = m.group(1).strip().upper()
    field_expr = m.group(2).strip()

    if agg_func.startswith("COUNT") and field_expr.startswith("DISTINCT "):
        result["agg"] = "COUNT_DISTINCT"
        cleaned = field_expr.replace("DISTINCT ", "").strip()
        result["field_expr"] = cleaned
        parts = cleaned.split(".")
        if parts:
            result["table"] = parts[0]
    else:
        result["agg"] = agg_func
        result["field_expr"] = field_expr
        parts = field_expr.split(".")
        if len(parts) >= 2:
            candidate = parts[0].strip()
            if candidate and candidate[0].isupper():
                result["table"] = candidate

    wm = re.search(r"WHERE\s+(.+)$", formula, re.I)
    if wm:
        result["where"] = wm.group(1).strip()

    return result


# ============================================================
# API 模式: 通过 ERP 接口查询（表驱动）
# ============================================================

# 端点类型 → 解析方式映射
# key: 端点路径片段, value: 解析类型
# 注意: 更具体的路径要放在前面（如 monthly-cost 在 monthly 之前）
ENDPOINT_PARSER = {
    "/stats/monthly-cost": "monthly_cost",  # data:[{period, sales, cost, orders}]
    "/stats/monthly": "monthly",        # data:[{period, orders, customers, amount}]
    "/stats/gross-profit": "gross_profit",  # {gross_profit_rate}
    "/stats/customer-top": "customer_top",  # data:[{name, orders, amount}]
    "/stats/product-top": "product_top",    # data:[{name, qty, amount, orders}]
    "/sales/summary": "sales_summary",      # {total_amount, customer_count, ...}
    "/sales/trend": "trend",                # data:[{period, amount}]
    "/data/query": "raw_query",             # {data:[...]}
    "/crm/retention-rate": "crm_retention", # {retention_rate|value}
    "/crm/new-customers": "crm_new",        # {new_customers|value}
    "/production/quality-rate": "quality",  # {quality_rate|value}
    "/production/rework-rate": "rework",    # {rework_rate|value}
    "/cashflow": "cashflow",                # {value}
    "/ar/aging": "ar_aging",                # {value}
    "/delivery/rate": "delivery_rate",      # {value}
    "/quality/defect": "quality_defect",    # {value}
}


def _resolve_endpoint(kpi: KPIDefinition, db_session, period: str) -> tuple:
    """从 data_source_config 表解析KPI的API端点。
    返回 (url, parser_type) 或 (None, None)
    """
    period_month = int(period[5:7])
    period_year = int(period[:4])

    # 1. 优先从 kpi.data_source_config JSON (source_ids) 查 data_source_config 表
    if db_session is not None:
        cfg = getattr(kpi, "data_source_config", None)
        if isinstance(cfg, dict) and cfg.get("source_ids"):
            for sid in cfg["source_ids"]:
                src = db_session.query(DataSourceConfig).filter(
                    DataSourceConfig.id == sid,
                    DataSourceConfig.status == "active",
                ).first()
                if src and src.api_endpoint:
                    url = src.api_endpoint
                    # 兼容 {period_year}/{period_month} 模板
                    url = url.replace("{period_year}", str(period_year)) \
                             .replace("{period_month}", str(period_month))
                    parser = _detect_parser(url)
                    logger.info(f"  [{kpi.kpi_code}] 表驱动: source_id={sid} → {url}")
                    return url, parser

    # 2. 静态映射保底（旧版硬编码，含真实KPI编码）
    url = STATIC_API_MAP.get(kpi.kpi_code)
    if url:
        url = url.replace("{period_year}", str(period_year)) \
                 .replace("{period_month}", str(period_month))
        return url, _detect_parser(url)

    return None, None


def _detect_parser(url: str) -> str:
    """根据URL路径推断解析类型"""
    for fragment, parser in ENDPOINT_PARSER.items():
        if fragment in url:
            return parser
    return "generic"


def _parse_response(kpi_code: str, parser: str, data: dict, period: str) -> float:
    """按端点类型解析响应，返回数值"""
    # 通用兜底
    if isinstance(data, (int, float)):
        return float(data)

    # data[] 数组类型
    rows = data.get("data", []) if isinstance(data, dict) else []

    if parser == "monthly":
        for m in rows:
            if m.get("period") == period:
                return float(m.get("amount", 0))
        return float(data.get("summary", {}).get("total_amount", 0)) if isinstance(data, dict) else 0

    elif parser == "monthly_cost":
        for m in rows:
            if m.get("period") == period:
                return float(m.get("cost", 0))
        return 0

    elif parser == "customer_top":
        return float(sum(c.get("amount", 0) for c in rows))

    elif parser == "product_top":
        return float(sum(c.get("amount", 0) for c in rows))

    elif parser == "trend":
        for m in rows:
            if m.get("period") == period:
                return float(m.get("amount", 0))
        return 0

    elif parser == "raw_query":
        # /data/query 返回 {data:[{...}]} 取第一行第一个数值
        if rows:
            first = rows[0]
            for v in first.values():
                if isinstance(v, (int, float)):
                    return float(v)
        return 0

    elif parser == "gross_profit":
        return float(data.get("gross_profit_rate", data.get("value", 0)))

    elif parser == "sales_summary":
        return float(data.get("total_amount", data.get("value", 0)))

    elif parser in ("crm_retention", "crm_new", "quality", "rework",
                    "cashflow", "ar_aging", "delivery_rate", "quality_defect"):
        # 所有财务/运营扩展端点统一返回 {value: xxx}
        return float(data.get("value", data.get("retention_rate", data.get("new_customers",
                     data.get("quality_rate", data.get("rework_rate", 0))))))

    # generic: 取常见字段
    if isinstance(data, dict):
        for key in ("value", "amount", "total", "actual_value", "result"):
            if key in data and data[key] is not None:
                return float(data[key])
    return 0.0


def fetch_via_api(kpi: KPIDefinition, parsed: dict, period: str, db_session=None) -> float:
    """通过 erp-api-gateway 查询ERP数据（表驱动）"""
    kpi_code = kpi.kpi_code

    url, parser = _resolve_endpoint(kpi, db_session, period)
    if not url:
        raise ValueError(f"未配置API映射: {kpi_code}")

    headers = {"X-API-Key": ERP_API_KEY, "User-Agent": "CMA-ERP-SYNC/1.0"}
    logger.info(f"  [{kpi_code}] API请求: {url} (parser={parser})")

    req = urllib.request.Request(url, headers=headers)
    try:
        with urllib.request.urlopen(req, timeout=15) as resp:
            data = json.loads(resp.read().decode())
    except urllib.error.HTTPError as e:
        raise ConnectionError(f"API返回 {e.code}: {e.read().decode()[:200]}")
    except Exception as e:
        raise ConnectionError(f"API请求失败: {e}")

    return _parse_response(kpi_code, parser, data, period)


# ============================================================
# Fallback 模式: 本地已有数据推算
# ============================================================

def fetch_fallback(kpi: KPIDefinition, parsed: dict, db_session, period: str) -> float:
    """Fallback: 从本地已有 kpi_values 推算或返回 None"""
    kpi_code = kpi.kpi_code

    # 对于已有数据的KPI，沿用最近月份的值（标注为estimated）
    existing = db_session.query(KPIValue).filter(
        KPIValue.kpi_id == kpi.id,
        KPIValue.source_type.in_(["erp", "manual"]),
    ).order_by(KPIValue.period.desc()).first()

    if existing and existing.actual_value is not None and existing.data_status == "verified":
        logger.info(f"  [{kpi_code}] Fallback: 沿用最近期 verified数据 {existing.period}={existing.actual_value}")
        return existing.actual_value

    # Bug3修复: 不沿用estimated旧值（可能失真，如4800万收入），避免污染当月数据
    if existing and existing.actual_value is not None and existing.data_status != "verified":
        logger.info(f"  [{kpi_code}] Fallback: 最近期 {existing.period} 为estimated({existing.actual_value})，不沿用，返回None")
        return None

    # 特殊KPI的默认值
    DEFAULTS = {
        "SALES_TOTAL": 800000,
        "CUSTOMER_COUNT": 25,
        "SALES_PROFIT_RATE": 25.0,
        "TOP5_CUSTOMER_RATIO": 50.0,
    }
    if kpi_code in DEFAULTS:
        logger.info(f"  [{kpi_code}] Fallback: 使用默认值 {DEFAULTS[kpi_code]}")
        return DEFAULTS[kpi_code]

    return None


# ============================================================
# TOP5_CUSTOMER_RATIO 的公式补充处理
# ============================================================

def compute_top5_ratio(db_session, period: str) -> float:
    """从 ERP schema 采集数据计算：前5客户销售额/总销售额*100"""
    # 先检查 erp_schema 是否有 MasterBill 的完整数据
    # 如果有物化数据，可以在这里做本地计算
    # 目前 erp_schema 只有元数据没有数据，返回 None 表示需要 API
    return None


# ============================================================
# 核心同步函数
# ============================================================

def sync_kpi(kpi: KPIDefinition, db_session, dry_run: bool = False,
             use_api: bool = True, target_period: str = None) -> bool:
    """同步单个KPI的ERP数据"""
    if kpi.data_source_type not in ("erp",):
        return False

    formula = kpi.formula
    if not formula:
        logger.warning(f"  [{kpi.kpi_code}] 无公式定义")
        return False

    parsed = parse_formula(formula)
    logger.info(f"  [{kpi.kpi_code}] 解析: table={parsed['table']}, agg={parsed['agg']}, "
                f"erp_direct={parsed.get('erp_direct',True)}")

    current_period = target_period if target_period else datetime.now().strftime("%Y-%m")

    # 尝试通过 API 获取
    value = None
    api_ok = False
    if use_api:
        try:
            value = fetch_via_api(kpi, parsed, current_period, db_session=db_session)
            if value is not None:
                api_ok = True
                logger.info(f"  [{kpi.kpi_code}] API结果: {current_period}={value}")
        except Exception as e:
            logger.warning(f"  [{kpi.kpi_code}] API失败: {e}")

    # API 失败则 fallback
    if not api_ok:
        try:
            value = fetch_fallback(kpi, parsed, db_session, current_period)
            if value is not None:
                source_note = "estimated"
                logger.info(f"  [{kpi.kpi_code}] Fallback结果: {current_period}={value}")
            else:
                logger.warning(f"  [{kpi.kpi_code}] 无可用数据, 跳过")
                return False
        except Exception as e:
            logger.error(f"  [{kpi.kpi_code}] Fallback失败: {e}")
            return False

    if dry_run:
        logger.info(f"  [{kpi.kpi_code}] DRY RUN: 跳过写入 value={value}")
        return True

    # 写入 kpi_values
    try:
        existing = db_session.query(KPIValue).filter(
            KPIValue.kpi_id == kpi.id,
            KPIValue.period == current_period,
            KPIValue.source_type == "erp",
        ).first()

        remark = f"ERP自动同步{' (API)' if api_ok else ' (估算)'} {datetime.now().strftime('%Y-%m-%d %H:%M')}"

        if existing:
            existing.actual_value = value
            existing.data_status = "verified" if api_ok else "estimated"
            existing.remark = remark
            existing.source_type = "erp"
            logger.info(f"  [{kpi.kpi_code}] 更新 {current_period}: {value}")
        else:
            kv = KPIValue(
                kpi_id=kpi.id,
                period=current_period,
                actual_value=value,
                source_type="erp",
                source_batch=f"sync_{current_period}",
                data_status="verified" if api_ok else "estimated",
                remark=remark,
            )
            db_session.add(kv)
            logger.info(f"  [{kpi.kpi_code}] 新增 {current_period}: {value}")

        db_session.commit()
        return True

    except Exception as e:
        db_session.rollback()
        logger.error(f"  [{kpi.kpi_code}] 写入失败: {e}")
        return False


def run_sync(dry_run: bool = False, kpi_codes: list = None, use_api: bool = True, period: str = None):
    """执行全部ERP KPI同步"""
    db = get_session_local()()
    try:
        query = db.query(KPIDefinition).filter(
            KPIDefinition.status == "active",
            KPIDefinition.data_source_type == "erp",
        )
        if kpi_codes:
            query = query.filter(KPIDefinition.kpi_code.in_(kpi_codes))

        kpis = query.all()
        target_period = period if period else datetime.now().strftime("%Y-%m")
        logger.info(f"开始同步ERP数据: {len(kpis)} 个KPI (API模式={use_api}, 期间={target_period})")

        success = 0
        fail = 0
        for kpi in kpis:
            if sync_kpi(kpi, db, dry_run, use_api, target_period):
                success += 1
            else:
                fail += 1

        if not dry_run:
            log = OperationLog(
                action="erp_sync",
                target_type="kpi",
                detail=json.dumps({
                    "total": len(kpis), "success": success,
                    "failed": fail, "api_mode": use_api,
                    "period": datetime.now().strftime("%Y-%m"),
                }, ensure_ascii=False),
            )
            db.add(log)
            db.commit()

        logger.info(f"同步完成: {success}成功 / {fail}失败 / {len(kpis)}总计")

    finally:
        db.close()


if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description="ERP数据同步")
    parser.add_argument("--dry-run", action="store_true", help="仅预览，不写入数据库")
    parser.add_argument("--kpi", nargs="+", help="指定KPI编码")
    parser.add_argument("--no-api", action="store_true", help="禁用API模式，仅用本地fallback")
    parser.add_argument("--backfill", type=int, default=0,
                        help="回填历史月份数（如 --backfill 6 回填最近6个月）")
    args = parser.parse_args()

    if args.backfill:
        from datetime import datetime, timedelta
        from app.database import get_session_local

        today = datetime.now()
        months_backfilled = 0
        for i in range(1, args.backfill + 1):
            # 计算目标月份
            m = today.month - i
            y = today.year
            while m <= 0:
                m += 12
                y -= 1
            period = f"{y}-{m:02d}"

            print(f"回填 {period}...")
            try:
                run_sync(dry_run=False, kpi_codes=args.kpi, use_api=not args.no_api, period=period)
                months_backfilled += 1
            except Exception as e:
                print(f"  {period} 失败: {e}")

        print(f"回填完成: {months_backfilled} 个月")
    else:
        run_sync(dry_run=args.dry_run, kpi_codes=args.kpi, use_api=not args.no_api)
