#!/usr/bin/env python3
import argparse
import json
import os
import subprocess
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path
from typing import Dict, List, Tuple


DEFAULT_APP_TOKEN = "SZ4ebCKU3a9OdfspetxcSeg1n4g"
DEFAULT_TABLE_ID = "tblzuMy85WiRAUX5"
DEFAULT_TABLE_LINK = (
    "https://acnuoqgha8fk.feishu.cn/wiki/"
    "RBSxwq8woiQDCUkRZfkcg7x8nMb?table=tblzuMy85WiRAUX5&view=vewuydm9EI"
)
OPENCLAW_CONFIG = "/root/.openclaw/openclaw.json"


REQUIRED_FIELDS = [
    "记录类型",
    "批次ID",
    "报表时间",
    "统计周期天数",
    "补货提前期天数",
    "预警阈值天数",
    "在售SKU数",
    "风险SKU数",
    "紧急SKU数",
    "预警SKU数",
    "SKU编码",
    "SKU名称",
    "SPU",
    "风险等级",
    "预计缺货天数",
    "建议补货件数",
    "可用库存",
    "近7天发货件数",
    "近30天发货件数",
    "近30天日均发货",
    "渠道",
    "店铺",
    "采购",
    "运营",
    "工厂",
    "仓库",
]


def _http_json(method: str, url: str, headers: Dict[str, str], payload: Dict = None) -> Dict:
    data = None
    req_headers = dict(headers)
    if payload is not None:
        data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
        req_headers["Content-Type"] = "application/json; charset=utf-8"

    req = urllib.request.Request(url, data=data, headers=req_headers, method=method)
    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            raw = resp.read().decode("utf-8")
            if not raw:
                return {}
            return json.loads(raw)
    except urllib.error.HTTPError as e:
        body = e.read().decode("utf-8", errors="ignore")
        raise RuntimeError(f"HTTP {e.code} {url}: {body[:1000]}") from e
    except Exception as e:
        raise RuntimeError(f"request failed {method} {url}: {e}") from e


def _load_feishu_app() -> Tuple[str, str]:
    app_id = os.getenv("FEISHU_APP_ID", "").strip()
    app_secret = os.getenv("FEISHU_APP_SECRET", "").strip()
    if app_id and app_secret:
        return app_id, app_secret

    cfg_path = Path(OPENCLAW_CONFIG)
    if not cfg_path.exists():
        raise RuntimeError("missing feishu credentials (env and openclaw.json both unavailable)")

    cfg = json.loads(cfg_path.read_text(encoding="utf-8"))
    ch = (cfg.get("channels") or {}).get("feishu") or {}
    app_id = (ch.get("appId") or "").strip()
    app_secret = (ch.get("appSecret") or "").strip()
    if not app_id or not app_secret:
        raise RuntimeError("feishu appId/appSecret is empty in openclaw.json")
    return app_id, app_secret


def _tenant_token(app_id: str, app_secret: str) -> str:
    url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
    res = _http_json(
        "POST",
        url,
        headers={},
        payload={"app_id": app_id, "app_secret": app_secret},
    )
    if res.get("code") != 0:
        raise RuntimeError(f"feishu auth failed: {res}")
    token = res.get("tenant_access_token")
    if not token:
        raise RuntimeError("feishu auth succeeded but tenant_access_token missing")
    return token


def _parse_company_rows(output: str) -> Dict[int, Dict]:
    result_sets: Dict[int, Dict] = {}
    current_idx = None
    current_cols: List[str] = []
    current_rows: List[Dict] = []

    def flush():
        nonlocal current_idx, current_cols, current_rows
        if current_idx is not None:
            result_sets[current_idx] = {"columns": current_cols, "rows": current_rows}
        current_idx = None
        current_cols = []
        current_rows = []

    for raw_line in output.splitlines():
        line = raw_line.strip()
        if not line:
            continue
        if line.startswith("RESULT_SET "):
            flush()
            current_idx = int(line.split()[1])
            continue
        if line.startswith("COLUMNS:"):
            cols = line.split(":", 1)[1].strip()
            current_cols = [c.strip() for c in cols.split(",")]
            continue
        if line.startswith("{") and line.endswith("}"):
            try:
                current_rows.append(json.loads(line))
            except Exception:
                pass
            continue
    flush()
    return result_sets


def _run_stock_risk(days: int, lead_days: int, warn_days: int, limit: int) -> Tuple[Dict, List[Dict]]:
    cmd = [
        "stock-risk-alerts",
        "--days",
        str(days),
        "--lead-days",
        str(lead_days),
        "--warn-days",
        str(warn_days),
        "--limit",
        str(limit),
    ]
    proc = subprocess.run(cmd, capture_output=True, text=True)
    if proc.returncode != 0:
        err = (proc.stderr or proc.stdout or "stock-risk-alerts failed").strip()
        raise RuntimeError(err)
    parsed = _parse_company_rows(proc.stdout or "")
    summary_rows = parsed.get(1, {}).get("rows", [])
    detail_rows = parsed.get(2, {}).get("rows", [])
    if not summary_rows:
        raise RuntimeError("stock-risk-alerts returned no summary rows")
    return summary_rows[0], detail_rows


def _list_fields(token: str, app_token: str, table_id: str) -> Dict[str, str]:
    url = (
        f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/fields"
        "?page_size=500"
    )
    res = _http_json("GET", url, headers={"Authorization": f"Bearer {token}"})
    if res.get("code") != 0:
        raise RuntimeError(f"list fields failed: {res}")
    items = (((res.get("data") or {}).get("items")) or [])
    out: Dict[str, str] = {}
    for it in items:
        name = it.get("field_name")
        fid = it.get("field_id")
        if name and fid:
            out[name] = fid
    return out


def _create_field(token: str, app_token: str, table_id: str, field_name: str) -> None:
    url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/fields"
    payload = {"field_name": field_name, "type": 1}
    res = _http_json("POST", url, headers={"Authorization": f"Bearer {token}"}, payload=payload)
    if res.get("code") != 0:
        raise RuntimeError(f"create field failed ({field_name}): {res}")


def _ensure_fields(token: str, app_token: str, table_id: str) -> None:
    fields = _list_fields(token, app_token, table_id)
    for name in REQUIRED_FIELDS:
        if name not in fields:
            _create_field(token, app_token, table_id, name)
    # wait a bit for schema consistency
    time.sleep(0.5)


def _create_record(token: str, app_token: str, table_id: str, fields: Dict[str, str]) -> str:
    url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records"
    payload = {"fields": fields}
    res = _http_json("POST", url, headers={"Authorization": f"Bearer {token}"}, payload=payload)
    if res.get("code") != 0:
        raise RuntimeError(f"create record failed: {res}")
    rec = (res.get("data") or {}).get("record") or {}
    return rec.get("record_id") or rec.get("id") or ""


def _fmt(v) -> str:
    if v is None:
        return ""
    if isinstance(v, float):
        return f"{v:.2f}".rstrip("0").rstrip(".")
    return str(v)


def main() -> int:
    parser = argparse.ArgumentParser(description="Push stock risk alerts to Feishu as structured rows")
    parser.add_argument("--days", type=int, default=30)
    parser.add_argument("--lead-days", type=int, default=10)
    parser.add_argument("--warn-days", type=int, default=15)
    parser.add_argument("--top", type=int, default=50, help="top N detail rows to push")
    parser.add_argument("--app-token", default=DEFAULT_APP_TOKEN)
    parser.add_argument("--table-id", default=DEFAULT_TABLE_ID)
    parser.add_argument("--table-link", default=DEFAULT_TABLE_LINK)
    args = parser.parse_args()

    days = max(7, min(args.days, 180))
    lead_days = max(1, min(args.lead_days, 60))
    warn_days = max(lead_days, min(args.warn_days, 90))
    top = max(1, min(args.top, 500))

    summary, details = _run_stock_risk(days, lead_days, warn_days, limit=max(top, 50))

    app_id, app_secret = _load_feishu_app()
    token = _tenant_token(app_id, app_secret)
    _ensure_fields(token, args.app_token, args.table_id)

    batch_id = time.strftime("%Y%m%d%H%M%S")
    now_text = time.strftime("%Y-%m-%d %H:%M:%S")

    summary_record = {
        "记录类型": "汇总",
        "批次ID": batch_id,
        "报表时间": now_text,
        "统计周期天数": _fmt(summary.get("lookback_days", days)),
        "补货提前期天数": _fmt(summary.get("replenishment_lead_days", lead_days)),
        "预警阈值天数": _fmt(summary.get("warning_days", warn_days)),
        "在售SKU数": _fmt(summary.get("sold_sku_count_30d")),
        "风险SKU数": _fmt(summary.get("stock_risk_sku_count")),
        "紧急SKU数": _fmt(summary.get("urgent_sku_count")),
        "预警SKU数": _fmt(summary.get("warning_sku_count")),
    }
    summary_record_id = _create_record(token, args.app_token, args.table_id, summary_record)

    detail_record_ids: List[str] = []
    for row in details[:top]:
        fields = {
            "记录类型": "明细",
            "批次ID": batch_id,
            "报表时间": now_text,
            "统计周期天数": _fmt(days),
            "补货提前期天数": _fmt(lead_days),
            "预警阈值天数": _fmt(warn_days),
            "SKU编码": _fmt(row.get("sku_id")),
            "SKU名称": _fmt(row.get("sku_name")),
            "SPU": _fmt(row.get("spu")),
            "风险等级": _fmt(row.get("stock_risk_level")),
            "预计缺货天数": _fmt(row.get("estimated_stockout_days")),
            "建议补货件数": _fmt(row.get("suggest_replenish_qty_to_lead_days")),
            "可用库存": _fmt(row.get("available_qty")),
            "近7天发货件数": _fmt(row.get("shipped_qty_7d")),
            "近30天发货件数": _fmt(row.get("shipped_qty_30d")),
            "近30天日均发货": _fmt(row.get("avg_daily_shipped_30d")),
            "渠道": _fmt(row.get("top_shop_channel")),
            "店铺": _fmt(row.get("top_shop_name")),
            "采购": _fmt(row.get("purchaser_name")),
            "运营": _fmt(row.get("operator_name")),
            "工厂": _fmt(row.get("factory_name")),
            "仓库": _fmt(row.get("warehouse_name")),
        }
        rec_id = _create_record(token, args.app_token, args.table_id, fields)
        detail_record_ids.append(rec_id)

    print("PUSH_STATUS: success")
    print(f"TABLE_LINK: {args.table_link}")
    print(f"BATCH_ID: {batch_id}")
    print(f"SUMMARY_RECORD_ID: {summary_record_id}")
    print(f"DETAIL_RECORD_COUNT: {len(detail_record_ids)}")
    if detail_record_ids:
        print(f"FIRST_DETAIL_RECORD_ID: {detail_record_ids[0]}")
    return 0


if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except Exception as e:
        print(f"PUSH_STATUS: failed\nERROR: {e}", file=sys.stderr)
        raise
