#!/usr/bin/env python3
"""通知分发器（v3.0 三通道任意组合）

支持三种渠道，可任意启用一个或多个组合：
  1. wechat_clawbot   微信 ClawBot（通过 Accio Work 的 notify_user tool）
  2. lark_cli         飞书 CLI（lark-cli IM 群组消息）
  3. dingtalk_cli     钉钉 CLI（dws chat send）

【配置文件】config/notify_channels.json
{
  "enabled_channels": ["wechat_clawbot", "lark_cli"],
  "wechat_clawbot": {
    "via_notify_user_tool": true        // 由 Accio 主 Agent 调 notify_user
  },
  "lark_cli": {
    "chat_id": "oc_xxxxxxxxxxxxxxxx",   // 飞书群 chat_id
    "cli_path": "lark-cli"              // 默认走 PATH
  },
  "dingtalk_cli": {
    "chat_id": "xxxxx",                 // 钉钉群 chat_id
    "cli_path": "dws"
  }
}

【使用方式】
  python scripts/notify_dispatcher.py --payload outputs/push_payloads/<file>.json
  python scripts/notify_dispatcher.py --message "测试消息" --channels lark_cli,dingtalk_cli
  python scripts/notify_dispatcher.py --test-all   # 给所有启用通道发测试消息

【路径变量】所有路径用 WORKSPACE_ROOT 自动推断，跨平台兼容。
"""
from __future__ import annotations
import argparse
import json
import os
import sys
import subprocess
import datetime
from pathlib import Path
from typing import List, Dict, Any

# === 自动推断 WORKSPACE_ROOT ===
WORKSPACE_ROOT = Path(__file__).resolve().parent.parent
CONFIG_DIR = WORKSPACE_ROOT / "config"
OUTPUTS_DIR = WORKSPACE_ROOT / "outputs"
PUSH_DIR = OUTPUTS_DIR / "push_payloads"
DISPATCH_LOG = OUTPUTS_DIR / "notify_dispatch_log.jsonl"

CONFIG_DIR.mkdir(exist_ok=True)
PUSH_DIR.mkdir(exist_ok=True)

CHANNELS_CONFIG_PATH = CONFIG_DIR / "notify_channels.json"

DEFAULT_CONFIG = {
    "enabled_channels": ["wechat_clawbot"],
    "wechat_clawbot": {
        "via_notify_user_tool": True,
        "_comment": "微信走 Accio Work 的 notify_user tool，由主 Agent 触发推送"
    },
    "lark_cli": {
        "chat_id": "",
        "cli_path": "lark-cli",
        "_comment": "填入飞书群 oc_ 开头的 chat_id；CLI 由 Accio Plugin lark-im 提供"
    },
    "dingtalk_cli": {
        "chat_id": "",
        "cli_path": "dws",
        "_comment": "填入钉钉群 chat_id；CLI 由 Accio Plugin dws 提供"
    }
}


# ============================================================================
# 配置加载
# ============================================================================

def load_config() -> dict:
    if not CHANNELS_CONFIG_PATH.exists():
        CHANNELS_CONFIG_PATH.write_text(
            json.dumps(DEFAULT_CONFIG, ensure_ascii=False, indent=2),
            encoding="utf-8"
        )
        print(f"⚠️  首次运行，已生成默认配置：{CHANNELS_CONFIG_PATH}")
        print(f"   请编辑该文件，按需启用通道并填入 chat_id。\n")
    return json.loads(CHANNELS_CONFIG_PATH.read_text(encoding="utf-8"))


def log_dispatch(payload: dict) -> None:
    """记录分发日志"""
    payload["dispatched_at"] = datetime.datetime.now().isoformat()
    with DISPATCH_LOG.open("a", encoding="utf-8") as f:
        f.write(json.dumps(payload, ensure_ascii=False) + "\n")


# ============================================================================
# 三个渠道适配器
# ============================================================================

def dispatch_wechat_clawbot(message: str, cfg: dict) -> Dict[str, Any]:
    """微信 ClawBot：写入待发队列，由 Accio Agent 调 notify_user 消费

    我们这里不直接调 Accio Work API，而是落地一个 .pending 文件，
    cron payload.kind=tool / tool=notify_user 会触发主 Agent 读取并发送。
    """
    timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S_%f")
    pending_file = PUSH_DIR / f"wechat_pending_{timestamp}.json"
    pending_payload = {
        "channel": "wechat_clawbot",
        "message": message,
        "queued_at": datetime.datetime.now().isoformat(),
        "dispatched": False,
        "via_notify_user_tool": cfg.get("via_notify_user_tool", True),
    }
    pending_file.write_text(json.dumps(pending_payload, ensure_ascii=False, indent=2), encoding="utf-8")
    return {"status": "queued", "channel": "wechat_clawbot", "file": str(pending_file)}


def dispatch_lark_cli(message: str, cfg: dict) -> Dict[str, Any]:
    """飞书 CLI：调用 lark-cli 发群消息"""
    chat_id = cfg.get("chat_id", "").strip()
    if not chat_id:
        return {"status": "skipped", "channel": "lark_cli", "reason": "chat_id 未配置"}

    cli = cfg.get("cli_path", "lark-cli")
    cmd = [cli, "im", "send-text", "--chat-id", chat_id, "--text", message]
    try:
        r = subprocess.run(cmd, capture_output=True, text=True, timeout=20)
        if r.returncode == 0:
            return {"status": "ok", "channel": "lark_cli", "chat_id": chat_id}
        return {"status": "failed", "channel": "lark_cli", "stderr": r.stderr[:200]}
    except FileNotFoundError:
        return {"status": "failed", "channel": "lark_cli", "reason": f"CLI 不存在：{cli}（需先安装 Accio Plugin lark-im）"}
    except subprocess.TimeoutExpired:
        return {"status": "failed", "channel": "lark_cli", "reason": "CLI 超时（20s）"}


def dispatch_dingtalk_cli(message: str, cfg: dict) -> Dict[str, Any]:
    """钉钉 CLI：调用 dws 发群消息"""
    chat_id = cfg.get("chat_id", "").strip()
    if not chat_id:
        return {"status": "skipped", "channel": "dingtalk_cli", "reason": "chat_id 未配置"}

    cli = cfg.get("cli_path", "dws")
    cmd = [cli, "chat", "send", "--chat-id", chat_id, "--text", message]
    try:
        r = subprocess.run(cmd, capture_output=True, text=True, timeout=20)
        if r.returncode == 0:
            return {"status": "ok", "channel": "dingtalk_cli", "chat_id": chat_id}
        return {"status": "failed", "channel": "dingtalk_cli", "stderr": r.stderr[:200]}
    except FileNotFoundError:
        return {"status": "failed", "channel": "dingtalk_cli", "reason": f"CLI 不存在：{cli}（需先安装 Accio Plugin dws）"}
    except subprocess.TimeoutExpired:
        return {"status": "failed", "channel": "dingtalk_cli", "reason": "CLI 超时（20s）"}


CHANNEL_HANDLERS = {
    "wechat_clawbot": dispatch_wechat_clawbot,
    "lark_cli": dispatch_lark_cli,
    "dingtalk_cli": dispatch_dingtalk_cli,
}


# ============================================================================
# 主分发逻辑
# ============================================================================

def dispatch(message: str, channels: List[str] = None) -> List[Dict[str, Any]]:
    """向指定渠道（或配置中启用的全部渠道）分发消息"""
    cfg = load_config()
    if channels is None:
        channels = cfg.get("enabled_channels", [])
    if not channels:
        print("⚠️  未启用任何通道。请编辑 config/notify_channels.json 的 enabled_channels")
        return []

    results = []
    for ch in channels:
        handler = CHANNEL_HANDLERS.get(ch)
        if not handler:
            results.append({"status": "unknown_channel", "channel": ch})
            continue
        ch_cfg = cfg.get(ch, {})
        r = handler(message, ch_cfg)
        results.append(r)
        log_dispatch({"channel": ch, "result": r, "message_preview": message[:100]})

        icon = {"ok": "✅", "queued": "📬", "skipped": "⏭", "failed": "❌", "unknown_channel": "❓"}.get(r["status"], "•")
        info = r.get("reason") or r.get("chat_id") or r.get("file") or ""
        print(f"  {icon} {ch:<18} {r['status']:<10} {info}")
    return results


def build_message_from_payload(payload_file: Path) -> str:
    """从 push_payload 文件构造可读消息"""
    data = json.loads(payload_file.read_text(encoding="utf-8"))
    if "message" in data and data["message"]:
        return data["message"]
    # 兼容评分输出
    meta = data.get("metadata", {})
    lines = [
        f"📨 新询盘 | {meta.get('grade', '未知')}",
        f"买家：{meta.get('buyer_name', '匿名')}（{meta.get('buyer_country', '?')}）",
        f"评分：{meta.get('score', '?')}（{meta.get('score_version', 'v3')}）",
        f"分配给：{meta.get('assignee', '待分配')}",
        f"询盘 ID：{data.get('inquiry_id', '?')}",
    ]
    return "\n".join(lines)


# ============================================================================
# CLI 入口
# ============================================================================

def main():
    p = argparse.ArgumentParser(description="通知分发器（v3.0 三通道）")
    p.add_argument("--payload", help="从 push_payload JSON 文件构造消息并分发")
    p.add_argument("--message", help="直接指定消息文本")
    p.add_argument("--channels", help="逗号分隔的通道列表，留空则用配置启用项")
    p.add_argument("--test-all", action="store_true", help="给所有启用通道发测试消息")
    p.add_argument("--show-config", action="store_true", help="显示当前通道配置")
    args = p.parse_args()

    if args.show_config:
        print(json.dumps(load_config(), ensure_ascii=False, indent=2))
        return

    if args.test_all:
        msg = f"📡 v3.0 通道连通测试 | {datetime.datetime.now().isoformat()}\n如果你看到这条，说明通道已贯通。"
        dispatch(msg)
        return

    if args.payload:
        msg = build_message_from_payload(Path(args.payload))
    elif args.message:
        msg = args.message
    else:
        sys.exit("❌ 需指定 --payload <file> 或 --message <text> 或 --test-all 之一")

    channels = args.channels.split(",") if args.channels else None
    dispatch(msg, channels)


if __name__ == "__main__":
    main()
