#!/usr/bin/env python3
"""
parse_schedule.py - 解析 Markdown 日程表，输出今日/近期事项、冲突检测结果

用法：
  python3 parse_schedule.py --file /home/node/.openclaw/workspace/日程.md [--mode today|tomorrow|week|all]
  python3 parse_schedule.py --stdin [--mode today]  # 从 stdin 读取 Markdown 内容

输出：JSON，包含 events / upcoming / conflicts / recurring / tasks
"""
from __future__ import annotations

import sys
import re
import json
import argparse
from datetime import datetime, date, timedelta
from typing import Optional, List, Dict


PRIORITY_ORDER = {"高": 0, "中": 1, "低": 2, "": 1}

TASK_KEYWORDS = {"提交", "截止", "deadline", "交付", "汇报", "发布", "上线"}


def parse_args():
    p = argparse.ArgumentParser(description="解析 Markdown 日程表")
    p.add_argument("--file", help="Markdown 日程文件路径")
    p.add_argument("--stdin", action="store_true", help="从 stdin 读取内容")
    p.add_argument(
        "--mode",
        choices=["today", "tomorrow", "week", "all"],
        default="today",
        help="筛选范围（默认 today）",
    )
    p.add_argument("--now", help="覆盖当前时间（格式 YYYY-MM-DD HH:MM，测试用）")
    p.add_argument(
        "--column-map",
        help=(
            "自定义列名映射，格式：'自定义列名=字段名,...'\n"
            "字段名可选：date/start/end/title/location/participants/priority/advance/note\n"
            "示例：--column-map '会期=date,主旨=title,时间段开始=start'"
        ),
    )
    return p.parse_args()


def read_content(args) -> str:
    if args.stdin:
        return sys.stdin.read()
    if args.file:
        path = args.file.replace("~", __import__("os").path.expanduser("~"))
        with open(path, encoding="utf-8") as f:
            return f.read()
    print("错误：请指定 --file 或 --stdin", file=sys.stderr)
    sys.exit(1)


def parse_global_config(content: str) -> dict:
    """从文件头部注释和引用块提取全局配置"""
    cfg = {
        "channel": "dingtalk",
        "default_advance_minutes": 15,
        "timezone": "Asia/Shanghai",
    }
    for line in content.splitlines():
        line = line.strip()
        m = re.match(r">\s*\*?\*?提醒渠道\*?\*?[：:]\s*(\S+)", line)
        if m:
            cfg["channel"] = m.group(1).strip()
        m = re.match(r">\s*\*?\*?默认提前提醒\*?\*?[：:]\s*(\d+)", line)
        if m:
            cfg["default_advance_minutes"] = int(m.group(1))
        m = re.match(r">\s*\*?\*?时区\*?\*?[：:]\s*(\S+)", line)
        if m:
            cfg["timezone"] = m.group(1)
    return cfg


def normalize_date(raw: str) -> Optional[date]:
    raw = raw.strip()
    for fmt in ("%Y-%m-%d", "%Y/%m/%d", "%m-%d", "%m/%d"):
        try:
            d = datetime.strptime(raw, fmt)
            if d.year == 1900:
                d = d.replace(year=date.today().year)
            return d.date()
        except ValueError:
            pass
    return None


def normalize_time(raw: str) -> Optional[int]:
    """返回分钟数（从当天0点起）"""
    raw = raw.strip().replace("：", ":")
    m = re.match(r"(\d{1,2}):(\d{2})", raw)
    if m:
        return int(m.group(1)) * 60 + int(m.group(2))
    return None


# ─────────────────────────────────────────────────────────
# 列名自动映射（宽松模式，支持不标准的 Excel/CSV 转换结果）
# ─────────────────────────────────────────────────────────

# 各字段的候选列名（支持中英文、简写、常见变体）
COLUMN_ALIASES = {
    "date":         {"日期", "date", "Date", "DATE", "时间", "日", "活动日期", "会议日期", "事件日期",
                     "日程日期", "计划日期"},
    "start":        {"开始时间", "开始", "start", "Start", "START", "开始时刻", "起始时间", "from", "From",
                     "开始时间段", "时间开始", "起始"},
    "end":          {"结束时间", "结束", "end", "End", "END", "结束时刻", "to", "To",
                     "时间结束", "截止", "终止"},
    "title":        {"事项", "标题", "title", "Title", "TITLE", "内容", "会议", "活动", "事件",
                     "subject", "Subject", "name", "Name", "任务", "描述", "会议名称", "活动名称",
                     "事项名称", "议题", "主题", "项目", "meeting", "Meeting", "event", "Event",
                     "topic", "Topic", "item", "Item"},
    "location":     {"地点", "location", "Location", "LOCATION", "地址", "会议室", "venue", "Venue",
                     "place", "Place", "room", "Room", "场所", "场地"},
    "participants": {"参与者", "人员", "participants", "Participants", "与会者", "成员", "attendees",
                     "Attendees", "负责人", "owner", "Owner", "参会人", "参会人员", "出席人员",
                     "相关人员", "联系人"},
    "priority":     {"优先级", "priority", "Priority", "PRIORITY", "重要程度", "紧急度", "级别",
                     "importance", "Importance", "urgency", "Urgency"},
    "advance":      {"提前(分钟)", "提前提醒", "提前", "advance", "Advance", "reminder", "Reminder",
                     "提前时间", "提醒时间", "提前分钟"},
    "note":         {"备注", "note", "Note", "NOTE", "notes", "Notes", "说明", "描述", "remark",
                     "Remark", "comments", "Comments", "附注", "补充", "其他"},
}


def parse_user_column_map(raw: str) -> dict:
    """
    解析用户自定义列名映射字符串。
    输入格式：'会期=date,主旨=title,时间段开始=start'
    返回：{'会期': 'date', '主旨': 'title', '时间段开始': 'start'}
    """
    VALID_FIELDS = {"date", "start", "end", "title", "location",
                    "participants", "priority", "advance", "note"}
    result = {}
    for part in raw.split(","):
        part = part.strip()
        if "=" not in part:
            continue
        custom_name, _, field = part.partition("=")
        custom_name = custom_name.strip()
        field = field.strip()
        if field in VALID_FIELDS and custom_name:
            result[custom_name] = field
    return result


def detect_column_mapping(header_cells: List[str],
                           user_map: Optional[dict] = None) -> dict:
    """
    根据表头自动推断列索引映射。
    优先级：用户自定义映射 > 精确别名匹配 > 子串匹配。
    返回 {字段名: 列索引}
    """
    mapping: dict = {}
    used_indices: set = set()

    # 第零轮：用户自定义映射（最高优先级）
    if user_map:
        for i, cell in enumerate(header_cells):
            cell_clean = cell.strip()
            if cell_clean in user_map:
                field = user_map[cell_clean]
                if field not in mapping:  # 同一字段只取第一个
                    mapping[field] = i
                    used_indices.add(i)

    # 第一轮：精确匹配
    for field, aliases in COLUMN_ALIASES.items():
        for i, cell in enumerate(header_cells):
            if i in used_indices:
                continue
            if cell.strip() in aliases:
                mapping[field] = i
                used_indices.add(i)
                break

    # 第二轮：子串匹配（仅对未匹配的字段和未使用的列）
    for field, aliases in COLUMN_ALIASES.items():
        if field in mapping:
            continue
        for i, cell in enumerate(header_cells):
            if i in used_indices:
                continue
            cell_clean = cell.strip()
            matched = False
            for alias in aliases:
                if not alias:
                    continue
                # 列名包含别名（如"会议名称"包含"名称"→title）
                # 但要避免短词误匹配（别名长度至少2）
                if len(alias) >= 2 and (alias in cell_clean or cell_clean in alias):
                    mapping[field] = i
                    used_indices.add(i)
                    matched = True
                    break
            if matched:
                break

    return mapping


def report_unresolved_columns(header_cells: List[str], mapping: dict) -> List[str]:
    """返回未能映射到任何已知字段的列名列表"""
    mapped_indices = set(mapping.values())
    return [
        header_cells[i].strip()
        for i in range(len(header_cells))
        if i not in mapped_indices and header_cells[i].strip()
    ]


def check_required_fields(mapping: dict) -> List[str]:
    """检查必要字段是否都已映射，返回缺失字段名"""
    required = ["date", "title"]
    return [f for f in required if f not in mapping]


def is_standard_header(cells: List[str]) -> bool:
    """判断是否是标准格式表头（首列精确为日期/Date/date）"""
    SCHEDULE_HEADERS = {"日期", "Date", "date"}
    return bool(cells) and cells[0].strip() in SCHEDULE_HEADERS


def is_any_schedule_header(cells: List[str]) -> bool:
    """宽松判断：表头中是否含有日期相关列"""
    date_aliases = COLUMN_ALIASES["date"]
    title_aliases = COLUMN_ALIASES["title"]
    has_date = any(c.strip() in date_aliases for c in cells)
    has_title = any(c.strip() in title_aliases for c in cells)
    return has_date and has_title


def extract_event_by_mapping(cells: List[str], mapping: dict, default_advance: int,
                              section_name: str) -> Optional[dict]:
    """根据列映射从数据行提取事项"""
    def get(field):
        idx = mapping.get(field)
        if idx is not None and idx < len(cells):
            return cells[idx].strip()
        return ""

    date_val = normalize_date(get("date"))
    if not date_val:
        return None

    title = get("title")
    if not title:
        return None

    start_str = get("start")
    end_str = get("end")
    start_min = normalize_time(start_str) if start_str else None
    end_min = normalize_time(end_str) if end_str else None

    priority_raw = get("priority")
    priority = priority_raw if priority_raw in ("高", "中", "低") else "中"

    advance_raw = get("advance")
    try:
        advance_min = int(advance_raw) if advance_raw else default_advance
    except ValueError:
        advance_min = default_advance

    return {
        "date": date_val.isoformat(),
        "start_min": start_min,
        "end_min": end_min,
        "start_str": start_str,
        "end_str": end_str,
        "title": title,
        "location": get("location"),
        "participants": get("participants"),
        "priority": priority,
        "advance_min": advance_min,
        "note": get("note"),
        "section": section_name,
        "is_task": any(k in title + get("note") for k in TASK_KEYWORDS),
    }


def parse_table_section(lines: List[str], section_name: str,
                         default_advance: int,
                         user_map: Optional[dict] = None) -> dict:
    """
    解析日程表格。返回 dict：
      events: 解析出的事项列表
      unresolved_columns: 无法映射的列名列表（宽松模式下）
      missing_required: 缺失的必要字段（date/title）
      mode: 'standard' | 'loose' | 'failed'
    """
    in_table = False
    header_found = False
    column_mapping: Optional[dict] = None
    loose_mode = False
    events = []
    unresolved_columns: List[str] = []
    missing_required: List[str] = []
    parse_mode = "failed"

    TASK_HEADERS = {"截止日期", "DeadLine", "deadline", "Deadline"}

    for line in lines:
        stripped = line.strip()
        if not stripped.startswith("|"):
            if in_table:
                in_table = False
                header_found = False
                column_mapping = None
                loose_mode = False
            continue

        cells = [c.strip() for c in stripped.strip("|").split("|")]
        if len(cells) < 2:
            continue

        # 跳过任务表头，不被主日程解析器误抓
        if cells[0].strip() in TASK_HEADERS:
            continue

        # 检测表头
        if not in_table:
            if is_standard_header(cells):
                START_HEADERS = {"开始时间", "开始", "start", "Start", "START", "起始时间"}
                second_col = cells[1].strip() if len(cells) > 1 else ""
                if second_col in START_HEADERS:
                    # 标准模式
                    in_table = True
                    header_found = True
                    loose_mode = False
                    column_mapping = None
                    parse_mode = "standard"
                    continue
                else:
                    # 首列是"日期"但列序不标准，降级为宽松模式
                    mapping = detect_column_mapping(cells, user_map)
                    missing = check_required_fields(mapping)
                    if not missing:
                        in_table = True
                        header_found = True
                        loose_mode = True
                        column_mapping = mapping
                        parse_mode = "loose"
                        unresolved_columns = report_unresolved_columns(cells, mapping)
                        missing_required = []
                    else:
                        missing_required = missing
                        parse_mode = "failed"
                    continue
            elif is_any_schedule_header(cells) or user_map:
                # 宽松模式：自动映射 + 用户自定义映射
                mapping = detect_column_mapping(cells, user_map)
                missing = check_required_fields(mapping)
                if not missing:
                    in_table = True
                    header_found = True
                    loose_mode = True
                    column_mapping = mapping
                    parse_mode = "loose"
                    unresolved_columns = report_unresolved_columns(cells, mapping)
                    missing_required = []
                else:
                    missing_required = missing
                    parse_mode = "failed"
                continue
            continue
            continue

        # 跳过分隔行
        if header_found and all(re.match(r"^[-:]+$", c) for c in cells if c):
            continue

        if not in_table:
            continue

        if loose_mode and column_mapping:
            # 宽松模式：用列映射提取
            ev = extract_event_by_mapping(cells, column_mapping, default_advance, section_name)
            if ev:
                events.append(ev)
        else:
            # 标准模式：按固定列序提取
            if len(cells) < 4:
                continue
            date_val = normalize_date(cells[0]) if cells[0] else None
            if not date_val:
                continue
            start_min = normalize_time(cells[1]) if len(cells) > 1 else None
            end_min = normalize_time(cells[2]) if len(cells) > 2 else None
            title = cells[3].strip() if len(cells) > 3 else ""
            location = cells[4].strip() if len(cells) > 4 else ""
            participants = cells[5].strip() if len(cells) > 5 else ""
            priority = cells[6].strip() if len(cells) > 6 else "中"
            advance_raw = cells[7].strip() if len(cells) > 7 else ""
            note = cells[8].strip() if len(cells) > 8 else ""
            if not title:
                continue
            try:
                advance_min = int(advance_raw) if advance_raw else default_advance
            except ValueError:
                advance_min = default_advance
            events.append({
                "date": date_val.isoformat(),
                "start_min": start_min,
                "end_min": end_min,
                "start_str": cells[1].strip() if len(cells) > 1 else "",
                "end_str": cells[2].strip() if len(cells) > 2 else "",
                "title": title,
                "location": location,
                "participants": participants,
                "priority": priority if priority in ("高", "中", "低") else "中",
                "advance_min": advance_min,
                "note": note,
                "section": section_name,
                "is_task": any(k in title + note for k in TASK_KEYWORDS),
            })

    return {
        "events": events,
        "mode": parse_mode,
        "unresolved_columns": unresolved_columns,
        "missing_required": missing_required,
    }


def parse_task_section(lines: List[str], default_advance: int) -> list:
    """解析待提醒任务表（截止日期形式）"""
    in_table = False
    header_found = False
    tasks = []

    TASK_HEADERS = {"截止日期", "DeadLine", "deadline", "Deadline"}

    for line in lines:
        stripped = line.strip()
        if not stripped.startswith("|"):
            if in_table:
                in_table = False
                header_found = False
            continue

        cells = [c.strip() for c in stripped.strip("|").split("|")]
        if not cells:
            continue

        if cells[0] in TASK_HEADERS:
            in_table = True
            header_found = True
            continue

        if header_found and all(re.match(r"^[-:]+$", c) for c in cells if c):
            continue

        if not in_table:
            continue

        if len(cells) < 2:
            continue

        date_val = normalize_date(cells[0]) if cells[0] else None
        if not date_val:
            continue

        title = cells[1].strip() if len(cells) > 1 else ""
        priority = cells[3].strip() if len(cells) > 3 else "中"
        advance_raw = cells[4].strip() if len(cells) > 4 else ""
        note = cells[5].strip() if len(cells) > 5 else ""

        # 解析"提前1天"、"提前2天"形式
        advance_min = default_advance
        m = re.search(r"提前\s*(\d+)\s*天", advance_raw)
        if m:
            advance_min = int(m.group(1)) * 24 * 60
        else:
            m = re.search(r"(\d+)", advance_raw)
            if m:
                advance_min = int(m.group(1))

        tasks.append({
            "date": date_val.isoformat(),
            "start_min": 9 * 60,  # 默认早上9点触发
            "end_min": 9 * 60 + 30,
            "start_str": "09:00",
            "end_str": "09:30",
            "title": f"📋 {title}",
            "location": "",
            "participants": cells[2].strip() if len(cells) > 2 else "",
            "priority": priority if priority in ("高", "中", "低") else "中",
            "advance_min": advance_min,
            "note": note,
            "section": "tasks",
            "is_task": True,
        })

    return tasks


def detect_conflicts(events: list[dict]) -> list[dict]:
    """检测同日时间冲突"""
    by_date: dict[str, list[dict]] = {}
    for e in events:
        if e["start_min"] is None or e["end_min"] is None:
            continue
        by_date.setdefault(e["date"], []).append(e)

    conflicts = []
    for day, day_events in by_date.items():
        sorted_events = sorted(day_events, key=lambda x: x["start_min"])
        for i in range(len(sorted_events)):
            for j in range(i + 1, len(sorted_events)):
                a, b = sorted_events[i], sorted_events[j]
                if a["end_min"] <= b["start_min"]:
                    # 不重叠，但检查是否过于紧凑（< 5分钟间隔）
                    gap = b["start_min"] - a["end_min"]
                    if 0 < gap < 5:
                        conflicts.append({
                            "type": "tight",
                            "date": day,
                            "event_a": a["title"],
                            "event_b": b["title"],
                            "a_time": f"{a['start_str']}–{a['end_str']}",
                            "b_time": f"{b['start_str']}–{b['end_str']}",
                            "gap_min": gap,
                            "message": (
                                f"⚠️ 紧凑安排：【{a['title']}】{a['start_str']}–{a['end_str']} "
                                f"与【{b['title']}】{b['start_str']} 之间仅 {gap} 分钟，几乎没有缓冲时间。"
                            ),
                        })
                else:
                    # 重叠
                    overlap = a["end_min"] - b["start_min"]
                    conflicts.append({
                        "type": "overlap",
                        "date": day,
                        "event_a": a["title"],
                        "event_b": b["title"],
                        "a_time": f"{a['start_str']}–{a['end_str']}",
                        "b_time": f"{b['start_str']}–{b['end_str']}",
                        "overlap_min": overlap,
                        "message": (
                            f"⚠️ 时间冲突：【{a['title']}】{a['start_str']}–{a['end_str']} "
                            f"与【{b['title']}】{b['start_str']}–{b['end_str']} "
                            f"重叠 {overlap} 分钟。"
                        ),
                    })
    return conflicts


def filter_upcoming(events: list, mode: str, now: datetime) -> list:
    """按 mode 筛选需要提醒的事项"""
    today = now.date()
    tomorrow = today + timedelta(days=1)
    week_end = today + timedelta(days=7)
    now_min = now.hour * 60 + now.minute

    result = []
    for e in events:
        try:
            ev_date = date.fromisoformat(e["date"])
        except ValueError:
            continue

        # 已过期跳过
        if ev_date < today:
            continue
        if ev_date == today and e["end_min"] is not None and e["end_min"] < now_min:
            continue

        # 低优先级且非任务，默认跳过
        if e["priority"] == "低" and not e["is_task"]:
            continue

        include = False
        if mode == "today" and ev_date == today:
            include = True
        elif mode == "tomorrow" and ev_date == tomorrow:
            include = True
        elif mode == "week" and today <= ev_date <= week_end:
            include = True
        elif mode == "all":
            include = True

        # today 模式额外：明日高优先级也纳入
        if mode == "today" and ev_date == tomorrow and e["priority"] == "高":
            include = True

        if include:
            result.append(e)

    result.sort(key=lambda x: (x["date"], x["start_min"] or 0))
    return result


def build_reminder_time(ev: dict, now: datetime) -> Optional[str]:
    """计算提醒时刻的 cron 表达式（单次）"""
    if ev["start_min"] is None:
        return None
    try:
        ev_date = date.fromisoformat(ev["date"])
    except ValueError:
        return None

    remind_total_min = ev["start_min"] - ev["advance_min"]
    if remind_total_min < 0:
        remind_total_min = 0

    r_hour = remind_total_min // 60
    r_min = remind_total_min % 60

    # 单次 cron：分 时 日 月 *
    return f"{r_min} {r_hour} {ev_date.day} {ev_date.month} *"


def build_reminder_time_str(ev: dict) -> str:
    """人类可读的提醒时刻"""
    if ev["start_min"] is None:
        return ""
    remind_total_min = max(0, ev["start_min"] - ev["advance_min"])
    r_hour = remind_total_min // 60
    r_min = remind_total_min % 60
    return f"{r_hour:02d}:{r_min:02d}"


def main():
    args = parse_args()
    content = read_content(args)
    cfg = parse_global_config(content)
    now = datetime.now()
    if args.now:
        try:
            now = datetime.strptime(args.now, "%Y-%m-%d %H:%M")
        except ValueError:
            pass

    lines = content.splitlines()

    # 解析用户自定义列名映射
    user_map = parse_user_column_map(args.column_map) if args.column_map else None

    # 解析主日程表
    schedule_result = parse_table_section(
        lines, "schedule", cfg["default_advance_minutes"], user_map
    )
    events = schedule_result["events"]
    # 解析任务表
    tasks = parse_task_section(lines, cfg["default_advance_minutes"])
    all_events = events + tasks

    # 冲突检测（仅对 schedule 类事项）
    conflicts = detect_conflicts(events)

    # 筛选近期/今日事项
    upcoming = filter_upcoming(all_events, args.mode, now)

    # 为每个事项计算提醒时刻
    for ev in upcoming:
        ev["remind_cron"] = build_reminder_time(ev, now)
        ev["remind_time_str"] = build_reminder_time_str(ev)

    output = {
        "meta": {
            "mode": args.mode,
            "now": now.strftime("%Y-%m-%d %H:%M"),
            "config": cfg,
            "parse_mode": schedule_result["mode"],
        },
        "summary": {
            "total_events": len(all_events),
            "upcoming_count": len(upcoming),
            "conflict_count": len(conflicts),
        },
        "upcoming": upcoming,
        "conflicts": conflicts,
    }

    # 列名问题提示（宽松模式下，让 agent 告知用户）
    if schedule_result["unresolved_columns"]:
        output["warnings"] = {
            "unresolved_columns": schedule_result["unresolved_columns"],
            "message": (
                f"⚠️ 以下列名无法自动识别，其内容已忽略："
                f"{', '.join(schedule_result['unresolved_columns'])}。"
                f"如需记录这些列，请告诉我它们对应的字段（如：'会期' = 日期，'主旨' = 事项）。"
            ),
        }
    if schedule_result["missing_required"]:
        output["errors"] = {
            "missing_required": schedule_result["missing_required"],
            "message": (
                f"❌ 表格缺少必要字段：{', '.join(schedule_result['missing_required'])}，"
                f"无法解析。请确认表格中包含日期列和事项/标题列。"
            ),
        }

    print(json.dumps(output, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()
