#!/usr/bin/env python3
"""
每日计划生成器。

执行时机：每天 07:50（由 OpenClaw Cron 触发）

流程：
  1. 从 config/task_registry.yaml 读取固定任务，注册到 OpenClaw Cron
  2. 从 Google Calendar 读取今天（或今天+明天）日程
  3. 为每个日程生成一次性提醒任务，注册到 OpenClaw Cron
  4. 生成 daily/cron-YYYYMMDD.md 可读记录

用法：
  python3 apps/generate_daily_plan.py
  python3 apps/generate_daily_plan.py --days 2        # 今天+明天
  python3 apps/generate_daily_plan.py --dry-run       # 只生成 md，不注册 cron
"""

from __future__ import annotations

import argparse
import json
import logging
import sys
from datetime import datetime
from pathlib import Path
try:
    from zoneinfo import ZoneInfo
except ImportError:
    from backports.zoneinfo import ZoneInfo

import yaml

# ── sys.path 保障（以脚本方式直接运行时）────────────────────────
sys.path.insert(0, str(Path(__file__).parent.parent))

from config.settings import (
    TIMEZONE,
    DAILY_DIR, TASK_REGISTRY_FILE, REMINDER_RULES_FILE,
    SKILL_BASE,
)
from gcal.models import CalendarEvent
from services.calendar_backend import active_provider, list_event_views
from services.time_window import get_today_window, get_today_and_tomorrow_window, now_tz
from services.reminder_planner import plan_reminders, ReminderPlan
from services.task_builder import (
    build_periodic_silent_task,
    build_reminder_task,
    build_reminder_task_name,
    build_report_task,
)
from services.conflict_detector import detect_conflicts, ConflictPair
from services.job_store import append_job_if_absent, list_job_names
from services.runtime_utils import expand_command_vars

logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
logger = logging.getLogger(__name__)

WEEKDAY_CN = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]


# ─────────────────────────── task registry ────────────────────────

def load_task_registry() -> list[dict]:
    try:
        with open(TASK_REGISTRY_FILE, encoding="utf-8") as f:
            data = yaml.safe_load(f) or {}
        provider = active_provider()
        return [
            task for task in data.get("tasks", [])
            if task.get("requires_provider", "any") in {"", "any", provider}
        ]
    except Exception as e:
        logger.error(f"加载 task_registry.yaml 失败: {e}")
        return []

def parse_fixed_task(task: dict, today: datetime) -> dict | None:
    """
    将 task_registry 中的一条任务转换为 OpenClaw job payload。
    返回 None 表示跳过（周期任务不在每日 at 中创建）。
    """
    name    = task.get("name", "")
    kind    = task.get("type", "report")
    message = expand_command_vars(task.get("message", task.get("command", "")))
    sched   = task.get("schedule", {})
    delete  = task.get("delete_after_run", True)

    if sched.get("kind") == "cron":
        # 周期任务：用 build_periodic_silent_task 或 build_report_task
        expr = sched.get("expr", "")
        tz   = sched.get("tz", TIMEZONE)
        if kind == "silent":
            return build_periodic_silent_task(name, expr, message, tz)
        else:
            return build_report_task(
                name, message,
                cron_expr=expr, tz=tz,
            )

    elif sched.get("kind") == "at":
        # 一次性任务：解析 HH:MM，换算 UTC
        time_str = sched.get("time", "")
        try:
            h, m = map(int, time_str.split(":"))
        except ValueError:
            logger.warning(f"无法解析时间 '{time_str}'，跳过任务 {name}")
            return None

        zone    = ZoneInfo(TIMEZONE)
        bj_time = today.astimezone(zone).replace(hour=h, minute=m, second=0, microsecond=0)

        if kind == "silent":
            # 静默一次性：用 agentTurn + delivery:none
            return {
                "name": name,
                "schedule": {"kind": "at", "at": _utc_str(bj_time)},
                "sessionTarget": "isolated",
                "payload": {"kind": "agentTurn", "message": message},
                "delivery": {"mode": "none"},
                "deleteAfterRun": delete,
            }
        else:
            return build_report_task(
                name, message,
                at_utc=bj_time, delete_after_run=delete,
            )

    return None


# ─────────────────────────── cron 注册 ────────────────────────────

def _utc_str(dt: datetime) -> str:
    from datetime import timezone
    return dt.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")


def _get_existing_task_names() -> set[str]:
    """从 jobs.json 读取已存在的任务名称"""
    try:
        return list_job_names()
    except Exception as e:
        logger.warning(f"读取现有 cron 任务失败: {e}")
    return set()


def register_cron_task(payload: dict, dry_run: bool, existing: set[str]) -> bool:
    """注册单个任务到 OpenClaw Cron（直接操作 jobs.json）。"""
    name = payload.get("name", "")
    if name in existing:
        logger.info(f"⏭️  任务已存在，跳过: {name}")
        return False

    if dry_run:
        logger.info(f"[dry-run] 将注册: {name}")
        existing.add(name)
        return True

    try:
        added = append_job_if_absent(payload)
        if added:
            existing.add(name)
            logger.info(f"✅ 已注册: {name}")
            return True
        logger.info(f"⏭️  任务已存在，跳过: {name}")
        return False
    except Exception as e:
        logger.error(f"❌ 注册异常 ({name}): {e}")
        return False


# ─────────────────────────── markdown 生成 ────────────────────────

def _load_reminder_rules_text() -> str:
    """从 reminder_rules.yaml 动态生成提醒规则说明文字。"""
    try:
        with open(REMINDER_RULES_FILE, encoding="utf-8") as f:
            data = yaml.safe_load(f) or {}
        lead = data.get("lead_minutes", {})
        type_cn = {"meeting": "会议", "sport": "运动", "meal": "饭局/宴会"}
        lines = []
        for key, cn in type_cn.items():
            mins = lead.get(key)
            if mins is not None:
                lines.append(f"- {cn}：提前 **{mins} 分钟** 提醒")
        default = data.get("default_lead_minutes")
        if default:
            lines.append(f"- 其他：提前 **{default} 分钟** 提醒（默认）")
        return "\n".join(lines) if lines else "- 默认：提前 5 分钟提醒"
    except Exception:
        return "- 会议：提前 **2 分钟** 提醒\n- 运动：提前 **15 分钟** 提醒\n- 饭局/宴会：提前 **15 分钟** 提醒"


def generate_markdown(
    today: datetime,
    fixed_tasks: list[dict],
    events: list[CalendarEvent],
    reminder_plans: list[ReminderPlan],
    conflicts: list[ConflictPair] | None = None,
) -> str:
    date_str = today.strftime("%Y-%m-%d")
    weekday  = WEEKDAY_CN[today.weekday()]
    lines    = [f"# Cron 任务记录 - {date_str} {weekday}", ""]

    # 提醒规则说明（动态读取）
    lines += ["## 提醒规则", "", _load_reminder_rules_text(), ""]

    # 冲突告警（有冲突时显示）
    if conflicts:
        lines += ["## ⚠️ 日程冲突", ""]
        for p in conflicts:
            lines.append(f"- {p.format_text()}")
        lines.append("")

    # 重复性任务
    lines += [
        "## 重复性任务汇总",
        "",
        "| 序号 | 任务 | 执行时间 | 类型 | 任务说明 |",
        "|------|------|----------|------|----------|",
    ]
    for i, t in enumerate(fixed_tasks, 1):
        sched = t.get("schedule", {})
        if sched.get("kind") == "cron":
            time_display = f"cron: {sched.get('expr', '-')}"
        else:
            time_display = sched.get("time", "-")
        lines.append(
            f"| {i} | {t.get('name','-')} | {time_display} | {t.get('type','-')} | {t.get('description','-')} |"
        )
    lines.append("")

    # 当日一次性提醒
    lines += ["## 当日一次性提醒", ""]
    if reminder_plans:
        lines += [
            "| 序号 | 日程 | 地点 | 开始时间 | 提醒时间 | 类型 | 状态 |",
            "|------|------|------|----------|----------|------|------|",
        ]
        for i, p in enumerate(reminder_plans, 1):
            start_str  = p.event.start.strftime("%H:%M")
            remind_str = p.remind_at.strftime("%H:%M")
            status     = "✅ 将创建" if p.should_schedule else "⏩ 已过期"
            location   = p.event.location or "-"
            lines.append(
                f"| {i} | {p.event.summary} | {location} | {start_str} | {remind_str} | {p.event_type} | {status} |"
            )
    else:
        lines.append("> 今日无日程提醒任务")
    lines.append("")

    # 今日日程概览
    lines += ["## 今日日程概览", ""]
    if events:
        for e in events:
            t    = e.start.strftime("%H:%M") if not e.is_all_day else "全天"
            loc  = f"  📍 {e.location}" if e.location else ""
            lines.append(f"- {t} {e.summary}{loc}")
    else:
        lines.append("- 暂无日程")
    lines.append("")

    lines += [
        "## 变更记录",
        "",
        f"- {today.strftime('%H:%M')} 自动生成今日 Cron 任务文件",
    ]

    return "\n".join(lines)


# ─────────────────────────── main ─────────────────────────────────

def main():
    parser = argparse.ArgumentParser(description="每日计划生成器")
    parser.add_argument("--days",    type=int, default=1,
                        help="拉取日程天数（1=今天，2=今天+明天）")
    parser.add_argument("--dry-run", action="store_true",
                        help="只生成 md 文件，不注册 cron 任务")
    args = parser.parse_args()

    now   = now_tz(TIMEZONE)
    today = now.replace(hour=0, minute=0, second=0, microsecond=0)

    # ── 1. 拉取日程 ────────────────────────────────────────────────
    if args.days >= 2:
        t_min, t_max = get_today_and_tomorrow_window(TIMEZONE)
    else:
        t_min, t_max = get_today_window(TIMEZONE)

    try:
        views = list_event_views(t_min, t_max)
        events = [item.event for item in views]
        logger.info(f"拉取到 {len(events)} 个事件")
    except Exception as e:
        logger.error(f"拉取日程失败: {e}")
        events = []

    # ── 2. 生成提醒计划 ─────────────────────────────────────────────
    plans = plan_reminders(events, now=now, tz=TIMEZONE)

    # ── 3. 冲突检测 ─────────────────────────────────────────────────
    conflicts = detect_conflicts(events)
    if conflicts:
        logger.warning(f"⚠️ 检测到 {len(conflicts)} 处日程冲突")
        for c in conflicts:
            logger.warning(f"   {c.format_text()}")

    # ── 4. 加载固定任务 ─────────────────────────────────────────────
    task_defs = load_task_registry()

    # ── 5. 注册 cron 任务 ──────────────────────────────────────────
    existing = set() if args.dry_run else _get_existing_task_names()

    # 固定任务
    for t in task_defs:
        payload = parse_fixed_task(t, today)
        if payload:
            register_cron_task(payload, args.dry_run, existing)

    # 日程提醒
    for p in plans:
        if not p.should_schedule:
            logger.info(f"⏩ 提醒时间已过，跳过: {p.event.summary}")
            continue
        payload = build_reminder_task(
            name         = build_reminder_task_name(p.event.summary, p.event.start, p.event.id),
            remind_at_utc= p.remind_at_utc,
            message      = p.reminder_message(),
        )
        register_cron_task(payload, args.dry_run, existing)

    # ── 6. 生成 md 文件 ─────────────────────────────────────────────
    DAILY_DIR.mkdir(parents=True, exist_ok=True)
    md_path = DAILY_DIR / f"cron-{today.strftime('%Y%m%d')}.md"
    md_content = generate_markdown(today, task_defs, events, plans, conflicts)
    md_path.write_text(md_content, encoding="utf-8")
    logger.info(f"📄 已生成: {md_path}")


if __name__ == "__main__":
    main()
