#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
poll_new_inquiries.py — 新询盘监听 + 推送 payload 生成器（首版）

设计取舍（v1）：
- 不在本脚本内直接 browser 抓取（cron 上下文无法 spawn sub-agent）
- 脚本职责：
  1. 扫描 outputs/score_*.json 中的所有评分结果
  2. 与 outputs/seen_inquiry_ids.json diff，识别新增 inquiry
  3. 为每条新增生成"微信推送文本"，写入 outputs/push_payloads/YYYYMMDD_HHMMSS_<inquiry_id>.json
  4. 更新 seen_inquiry_ids.json
  5. 写 polling_status.json 记录本次执行
- 实际微信发送：cron 通过 payload.kind=tool / tool=notify_user 触发，由 Agent 读取最新 push_payload 文件并调用

后续 v2 增强：
- 加 browser 抓取链路（poll 模式）
- 加 chat_signals 变化的 is_re_evaluation
- 加全局熔断 / 暂停开关

使用：
  python -X utf8 scripts/poll_new_inquiries.py
  python -X utf8 scripts/poll_new_inquiries.py --dry-run        # 不写文件，只打印
  python -X utf8 scripts/poll_new_inquiries.py --test-push      # 强制生成一条测试 payload
"""

import argparse
import json
import os
import sys
from datetime import datetime, timezone, timedelta
from pathlib import Path

# 路径
SCRIPT_DIR = Path(__file__).resolve().parent
ROOT = SCRIPT_DIR.parent
OUTPUTS = ROOT / "outputs"
SEEN_FILE = OUTPUTS / "seen_inquiry_ids.json"
PAUSE_FLAG = OUTPUTS / "pause.flag"
PUSH_DIR = OUTPUTS / "push_payloads"
STATUS_FILE = OUTPUTS / "polling_status.json"

# 时区
TZ_CN = timezone(timedelta(hours=8))


def now_iso():
    return datetime.now(TZ_CN).strftime("%Y-%m-%dT%H:%M:%S+08:00")


def now_compact():
    return datetime.now(TZ_CN).strftime("%Y%m%d_%H%M%S")


def load_json(path, default=None):
    """安全读 JSON，文件不存在或解析失败返回 default"""
    try:
        if not path.exists():
            return default
        with open(path, "r", encoding="utf-8") as f:
            return json.load(f)
    except (json.JSONDecodeError, OSError) as e:
        print(f"[WARN] 读取 {path} 失败: {e}", file=sys.stderr)
        return default


def save_json(path, data):
    """写 JSON，自动建目录"""
    path.parent.mkdir(parents=True, exist_ok=True)
    with open(path, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=2)


def scan_score_files():
    """扫描 outputs/ 下所有 score_*.json，提取 inquiry_id → 评分结果 映射

    返回: {inquiry_id: result_dict_with_meta}
    优先取最新版本（v3 > v2 > 无版本）
    """
    if not OUTPUTS.exists():
        return {}

    all_inquiries = {}

    for score_file in sorted(OUTPUTS.glob("score_*.json")):
        # 跳过对比文件 / 30 条批跑结果（基线用）
        if "compare" in score_file.name or score_file.name == "score_30.json":
            continue

        data = load_json(score_file)
        if not data:
            continue

        # 兼容两种结构：{results: [...]} 或单条 dict
        results = data.get("results") if isinstance(data, dict) else None
        if results is None and isinstance(data, dict) and "inquiry_id" in data:
            results = [data]
        if not results:
            continue

        # 推断版本
        version = "v3" if "v3" in score_file.name else "v2"

        for r in results:
            iid = str(r.get("inquiry_id", "")).strip()
            if not iid:
                continue
            existing = all_inquiries.get(iid)
            # 已存在且版本更高 → 跳过；否则覆盖
            if existing and existing["score_version"] == "v3" and version == "v2":
                continue
            all_inquiries[iid] = {
                "result": r,
                "score_version": version,
                "source_file": score_file.name,
            }

    return all_inquiries


def format_push_message(inquiry, result, version):
    """生成微信推送的纯文本消息（控制在 ~300 字以内）"""
    grade = result.get("grade", "?")
    score = result.get("score", "?")
    buyer = result.get("buyer_name", "?")
    country = result.get("buyer_country", "?")
    product = result.get("product", "?")
    assignee = result.get("assignee", "?")
    next_action = result.get("next_action", "")
    reasons = result.get("reasons", [])

    # 紧迫度图标
    icon = {"S": "🔴", "A": "🟠", "B": "🟡", "C": "🔵", "无效": "⚪"}.get(grade, "❓")

    # 截断 product
    if len(product) > 60:
        product = product[:57] + "..."

    # 关键评分理由（取前 3 条正分）
    top_reasons = []
    for reason in reasons:
        if "+" in reason and "+0" not in reason:
            top_reasons.append(reason)
        if len(top_reasons) >= 3:
            break

    msg_lines = [
        f"{icon} 新询盘 · {grade} 级（{score}分 / {version}）",
        f"买家：{buyer}（{country}）",
        f"商品：{product}",
        f"负责人：{assignee}",
        "",
        f"📌 建议动作：{next_action}",
    ]
    if top_reasons:
        msg_lines.append("")
        msg_lines.append("评分要点：")
        for r in top_reasons:
            msg_lines.append(f"  · {r}")
    msg_lines.append("")
    msg_lines.append("⚠️ 待业务员人工确认；规则未锁定前评级仅供参考")

    return "\n".join(msg_lines)


def write_push_payload(inquiry_id, message, result):
    """落盘推送 payload"""
    PUSH_DIR.mkdir(parents=True, exist_ok=True)
    fname = f"{now_compact()}_{inquiry_id}.json"
    payload = {
        "generated_at": now_iso(),
        "inquiry_id": inquiry_id,
        "channel": "wechat_clawbot",
        "message": message,
        "metadata": {
            "grade": result.get("grade"),
            "score": result.get("score"),
            "buyer_name": result.get("buyer_name"),
            "assignee": result.get("assignee"),
        },
        "auto_triggered": True,
        "dispatched": False,  # 由 Agent 调 notify_user 后改 true
    }
    fpath = PUSH_DIR / fname
    save_json(fpath, payload)
    return fpath


def write_status(stats, errors=None, paused=False):
    """落盘本次执行状态"""
    status = {
        "last_run_at": now_iso(),
        "status": "paused" if paused else ("error" if errors else "ok"),
        "stats": stats,
        "errors": errors or [],
    }
    save_json(STATUS_FILE, status)
    return status


def main():
    parser = argparse.ArgumentParser(description="新询盘 diff + 推送 payload 生成")
    parser.add_argument("--dry-run", action="store_true", help="不写文件，只打印")
    parser.add_argument("--test-push", action="store_true",
                        help="强制为最高分的已 seen 询盘生成一条测试 payload（用于通道验证）")
    args = parser.parse_args()

    print(f"[poll] 开始执行 @ {now_iso()}")

    # 暂停开关
    if PAUSE_FLAG.exists():
        print(f"[poll] 检测到 {PAUSE_FLAG}，暂停退出")
        if not args.dry_run:
            write_status({"skipped": True}, paused=True)
        return 0

    # 读 seen
    seen_data = load_json(SEEN_FILE, default={
        "last_updated": now_iso(),
        "schema_version": "v1",
        "seen": {},
        "stats": {"total_seen": 0, "by_grade": {}}
    })
    seen_ids = set(seen_data.get("seen", {}).keys())
    print(f"[poll] 已 seen: {len(seen_ids)} 条")

    # 扫描评分文件
    all_inquiries = scan_score_files()
    print(f"[poll] score 文件扫描: 共 {len(all_inquiries)} 条 unique inquiry_id")

    # diff
    new_ids = set(all_inquiries.keys()) - seen_ids
    print(f"[poll] 新增: {len(new_ids)} 条 → {sorted(new_ids)}")

    payloads_generated = []
    errors = []

    # 真实新增 → 生成 payload
    for iid in sorted(new_ids):
        entry = all_inquiries[iid]
        try:
            msg = format_push_message(iid, entry["result"], entry["score_version"])
            print(f"\n[poll] 新增 {iid} 推送预览:\n{msg}\n")
            if not args.dry_run:
                fpath = write_push_payload(iid, msg, entry["result"])
                payloads_generated.append(str(fpath.relative_to(ROOT)))
                # 更新 seen
                r = entry["result"]
                seen_data["seen"][iid] = {
                    "buyer_name": r.get("buyer_name"),
                    "first_seen_at": now_iso(),
                    "last_processed_at": now_iso(),
                    "score": r.get("score"),
                    "grade": r.get("grade"),
                    "score_version": entry["score_version"],
                    "processing_status": "completed",
                    "last_message_count": None,
                    "retry_count": 0,
                }
        except Exception as e:
            err = f"处理 {iid} 失败: {e}"
            print(f"[ERROR] {err}", file=sys.stderr)
            errors.append(err)

    # 测试模式：强制生成 1 条
    if args.test_push:
        if not seen_data["seen"]:
            print("[poll] --test-push 但无任何 seen 数据，跳过")
        else:
            # 取分数最高的一条
            test_iid = max(seen_data["seen"].keys(),
                           key=lambda k: seen_data["seen"][k].get("score", 0))
            test_meta = seen_data["seen"][test_iid]
            test_msg_lines = [
                "🧪 【cron 通道测试】",
                f"时间：{now_iso()}",
                f"测试样本：{test_meta.get('buyer_name')} ({test_iid})",
                f"评级：{test_meta.get('grade')}（{test_meta.get('score')}分 / {test_meta.get('score_version')}）",
                "",
                "如果你在微信看到这条消息，说明 cron + notify_user + 微信 ClawBot 通道全部贯通。",
                "下一步：等真实新询盘到达，会自动按相同链路推送。",
            ]
            test_msg = "\n".join(test_msg_lines)
            print(f"\n[poll][test] 测试推送预览:\n{test_msg}\n")
            if not args.dry_run:
                fake_result = {
                    "grade": test_meta.get("grade"),
                    "score": test_meta.get("score"),
                    "buyer_name": test_meta.get("buyer_name"),
                    "assignee": "TEST",
                }
                fpath = write_push_payload(f"TEST_{test_iid}", test_msg, fake_result)
                payloads_generated.append(str(fpath.relative_to(ROOT)))

    # 更新 seen
    if not args.dry_run and new_ids:
        seen_data["last_updated"] = now_iso()
        seen_data["stats"]["total_seen"] = len(seen_data["seen"])
        grade_counts = {}
        for v in seen_data["seen"].values():
            g = v.get("grade", "?")
            grade_counts[g] = grade_counts.get(g, 0) + 1
        seen_data["stats"]["by_grade"] = grade_counts
        save_json(SEEN_FILE, seen_data)
        print(f"[poll] 已更新 seen_inquiry_ids.json")

    # 写状态
    stats = {
        "scanned_inquiries": len(all_inquiries),
        "already_seen": len(seen_ids),
        "new_added": len(new_ids),
        "payloads_generated": len(payloads_generated),
        "payload_files": payloads_generated,
    }
    if not args.dry_run:
        write_status(stats, errors=errors)

    print(f"\n[poll] 完成 — 新增 {len(new_ids)} / 推送 {len(payloads_generated)} / 错误 {len(errors)}")
    return 0 if not errors else 1


if __name__ == "__main__":
    sys.exit(main())
