from __future__ import annotations

import argparse
import json
import os
import shutil
import sys
import time
from pathlib import Path
from typing import Any, Dict, Optional

from store_db.db import connect
from store_db.operation_log import commit_with_log, log_read
from store_db.response import emit_error, emit_need_confirm, emit_ok

from store_schedule_lib.change_monitor import save_snapshot_hash, summarize_changes
from store_schedule_lib.config_loader import load_task_registry
from store_schedule_lib.cron_registry import (
    list_store_jobs,
    register_job,
    remove_job_by_name,
    resolve_jobs_path,
)
from store_schedule_lib.daily_plan import run_daily_plan
from store_schedule_lib.daily_summary import REMINDER_TYPES, build_daily_summary, run_query
from store_schedule_lib.reminder_planner import build_plans, schedule_plans
from store_schedule_lib.task_builder import build_cron_task, build_report_task

SKILL = "store-schedule"
SKILL_ROOT = Path(__file__).resolve().parent.parent
CRON_TEMPLATE = SKILL_ROOT / "references" / "store_cron.template.json"
DEFAULT_SKILL_CLI = "uv run {baseDir}/scripts/cli.py"


def _resolve_skill_cli() -> str:
    return os.environ.get("STORE_SCHEDULE_CLI", DEFAULT_SKILL_CLI).strip() or DEFAULT_SKILL_CLI


def _format_task_message(message: str) -> str:
    return message.replace("{skill_cli}", _resolve_skill_cli())


def _resolve_workspace_path(args: argparse.Namespace) -> Path:
    if args.path:
        return Path(args.path).expanduser().resolve()
    for key in ("WORKSPACE", "OPENCLAW_WORKSPACE"):
        val = os.environ.get(key, "").strip()
        if val:
            return Path(val).expanduser().resolve()
    return Path.cwd().resolve()


def _reject_forbidden_reminder_source(args: argparse.Namespace) -> None:
    src = getattr(args, "source", None)
    if src in ("fields_json", "note", "fields"):
        emit_error(
            "reminder_source_forbidden",
            {
                "source": src,
                "hint": "提醒仅读固定列（birthday/due_date/next_followup_date 等），勿从 fields_json 或 note 推断",
            },
        )


def _parse_json_payload(raw: Optional[str]) -> Dict[str, Any]:
    if not raw:
        return {}
    return json.loads(raw)


def cmd_daily_summary(args: argparse.Namespace) -> int:
    _reject_forbidden_reminder_source(args)
    t0 = time.perf_counter()
    conn = connect()
    body = build_daily_summary(conn, args.date, mark_sent=args.mark_sent)
    duration_ms = int((time.perf_counter() - t0) * 1000)
    if args.mark_sent:
        save_snapshot_hash(conn)
        commit_with_log(
            conn, args, skill=SKILL, command="daily-summary", op="update", table_name="sent_reminders",
            duration_ms=duration_ms,
        )
    else:
        log_read(
            conn, args, skill=SKILL, command="daily-summary", table_name="sent_reminders",
            duration_ms=duration_ms,
        )
    emit_ok(body)
    return 0


def cmd_reminder_query(args: argparse.Namespace) -> int:
    _reject_forbidden_reminder_source(args)
    t0 = time.perf_counter()
    rtype = args.type
    if rtype not in REMINDER_TYPES:
        emit_error("invalid_reminder_type", {"type": rtype, "allowed": sorted(REMINDER_TYPES)})
    conn = connect()
    items = run_query(conn, rtype, args.date, days=args.days)
    block = {"type": rtype, "count": len(items), "items": items}
    log_read(
        conn, args, skill=SKILL, command="reminder-query", table_name="sent_reminders",
        duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"date": args.date, "block": block})
    return 0


def cmd_reminder_plan(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    payload = _parse_json_payload(args.json)
    kinds = payload.get("kinds") or ["birthday", "member_due", "card_expiring"]
    try:
        plans = build_plans(conn, kinds, payload)
    except ValueError as e:
        code = str(e)
        if code == "reminder_kind_deprecated":
            emit_error(code, {"hint": "请改用 member-due 查询 + followup-add；dynamic_due 已废弃"})
        elif code == "invalid_reminder_kind":
            emit_error(code, {"kinds": kinds})
        else:
            raise
    items = [
        {
            "member_id": p.member_id,
            "member_name": p.member_name,
            "reminder_type": p.reminder_type,
            "title": p.title,
            "cron_expr": p.cron_expr,
            "message": p.message,
            "remind_at": p.remind_at or p.trigger_at,
        }
        for p in plans
    ]
    log_read(
        conn, args, skill=SKILL, command="reminder-plan", table_name="store_members",
        duration_ms=int((time.perf_counter() - t0) * 1000),
    )
    emit_ok({"count": len(items), "plans": items})
    return 0


def cmd_reminder_schedule(args: argparse.Namespace) -> int:
    if not args.yes and not args.dry_run:
        emit_need_confirm({"action": "reminder-schedule"})
        return 0
    t0 = time.perf_counter()
    conn = connect()
    payload = _parse_json_payload(args.json)
    kinds = payload.get("kinds") or ["birthday"]
    try:
        plans = build_plans(conn, kinds, payload)
        scheduled = schedule_plans(
            conn,
            plans,
            dry_run=args.dry_run,
            register_appt="appt" in kinds,
        )
    except ValueError as e:
        code = str(e)
        if code in ("reminder_kind_deprecated", "invalid_reminder_kind"):
            emit_error(code, {"kinds": kinds})
        else:
            raise
    except OSError:
        emit_error("cron_unavailable", {"hint": "jobs.json not writable and openclaw CLI unavailable"})
        return 0

    mode = "daily_plan"
    if not args.dry_run:
        commit_with_log(
            conn, args, skill=SKILL, command="reminder-schedule", op="create", table_name="sent_reminders",
            after_json={"count": len(scheduled), "mode": mode},
            duration_ms=int((time.perf_counter() - t0) * 1000),
        )
    else:
        log_read(
            conn, args, skill=SKILL, command="reminder-schedule", table_name="sent_reminders",
            duration_ms=int((time.perf_counter() - t0) * 1000),
        )
    emit_ok({"count": len(scheduled), "mode": mode, "items": scheduled})
    return 0


def cmd_daily_plan(args: argparse.Namespace) -> int:
    t0 = time.perf_counter()
    conn = connect()
    body = run_daily_plan(
        conn,
        args.date,
        days=args.days,
        dry_run=args.dry_run,
        appt_only=args.register_appt_only,
    )
    if not args.dry_run:
        commit_with_log(
            conn, args, skill=SKILL, command="daily-plan", op="create", table_name="sent_reminders",
            after_json={"count": body.get("count", 0)},
            duration_ms=int((time.perf_counter() - t0) * 1000),
        )
    else:
        log_read(
            conn, args, skill=SKILL, command="daily-plan", table_name="sent_reminders",
            duration_ms=int((time.perf_counter() - t0) * 1000),
        )
    emit_ok(body)
    return 0


def cmd_reminder_list(args: argparse.Namespace) -> int:
    jobs = list_store_jobs()
    emit_ok({"count": len(jobs), "jobs": jobs, "jobs_path": str(resolve_jobs_path())})
    return 0


def cmd_reminder_remove(args: argparse.Namespace) -> int:
    removed = remove_job_by_name(args.name)
    if not removed:
        emit_error("not_found", {"name": args.name})
    emit_ok({"removed": True, "name": args.name})
    return 0


def cmd_change_check(args: argparse.Namespace) -> int:
    conn = connect()
    lines = summarize_changes(conn)
    if not lines:
        emit_ok({"changed": False, "messages": []})
        return 0
    emit_ok({"changed": True, "messages": lines})
    return 0


def cmd_cron_init(args: argparse.Namespace) -> int:
    if not CRON_TEMPLATE.is_file():
        emit_error("not_found", {"file": str(CRON_TEMPLATE)})
    workspace = _resolve_workspace_path(args)
    target = workspace / "store_cron.json"
    if target.is_file() and not args.force:
        emit_ok({"skipped": True, "path": str(target)})
        return 0
    workspace.mkdir(parents=True, exist_ok=True)
    shutil.copy2(CRON_TEMPLATE, target)
    emit_ok({"created": True, "path": str(target)})
    return 0


def cmd_cron_setup(args: argparse.Namespace) -> int:
    if not args.yes:
        emit_need_confirm({"action": "cron-setup"})
        return 0
    init_args = argparse.Namespace(path=args.path, force=False)
    cmd_cron_init(init_args)
    registered = []
    for task in load_task_registry():
        name = task.get("name", "")
        sched = task.get("schedule", {})
        expr = sched.get("expr", "")
        tz = sched.get("tz", "Asia/Shanghai")
        message = _format_task_message(task.get("message", ""))
        delete = task.get("delete_after_run", False)
        if task.get("type") == "report":
            payload = build_report_task(name, message, cron_expr=expr, tz=tz, delete_after_run=delete)
        else:
            payload = build_cron_task(name, expr, message, tz=tz, delete_after_run=delete)
        ok = register_job(payload)
        registered.append({"name": name, "scheduled": ok})
    workspace = _resolve_workspace_path(args)
    cron_path = workspace / "store_cron.json"
    if cron_path.is_file():
        data = json.loads(cron_path.read_text(encoding="utf-8"))
        data.setdefault("daily", {})["enabled"] = True
        if "daily_plan" in data:
            data["daily_plan"]["enabled"] = True
        if isinstance(data.get("appt_reminders"), dict):
            data["appt_reminders"]["enabled"] = True
        cron_path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
    emit_ok({"registered": registered, "store_cron": str(cron_path)})
    return 0


def build_parser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(description="Store schedule CLI")
    sub = p.add_subparsers(dest="cmd", required=True)

    s = sub.add_parser("daily-summary")
    s.add_argument("--date", default=None, help="YYYY-MM-DD，默认今天")
    s.add_argument("--mark-sent", action="store_true")
    s.add_argument("--md", action="store_true", help="保留 markdown 字段")
    s.add_argument("--source")
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_daily_summary)

    s = sub.add_parser("reminder-query")
    s.add_argument("--type", required=True, dest="type")
    s.add_argument("--date", default=None)
    s.add_argument("--days", type=int, default=None)
    s.add_argument("--source")
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_reminder_query)

    rem = sub.add_parser("reminder")
    rem_sub = rem.add_subparsers(dest="reminder_cmd", required=True)

    s = rem_sub.add_parser("plan")
    s.add_argument("--json")
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_reminder_plan)

    s = rem_sub.add_parser("schedule")
    s.add_argument("--json")
    s.add_argument("--yes", action="store_true")
    s.add_argument("--dry-run", action="store_true")
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_reminder_schedule)

    s = rem_sub.add_parser("list")
    s.set_defaults(func=cmd_reminder_list)

    s = rem_sub.add_parser("remove")
    s.add_argument("--name", required=True)
    s.set_defaults(func=cmd_reminder_remove)

    s = sub.add_parser("daily-plan")
    s.add_argument("--date", default=None)
    s.add_argument("--days", type=int, default=1)
    s.add_argument("--dry-run", action="store_true")
    s.add_argument("--register-appt-only", action="store_true")
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_daily_plan)

    s = sub.add_parser("change-check")
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_change_check)

    s = sub.add_parser("cron-init")
    s.add_argument("--path", default=None)
    s.add_argument("--force", action="store_true")
    s.set_defaults(func=cmd_cron_init)

    s = sub.add_parser("cron-setup")
    s.add_argument("--path", default=None)
    s.add_argument("--yes", action="store_true")
    s.add_argument("--hour", type=int, default=7)
    s.add_argument("--session-id")
    s.set_defaults(func=cmd_cron_setup)

    return p


def main() -> int:
    from datetime import datetime

    args = build_parser().parse_args()
    if getattr(args, "date", None) is None:
        args.date = datetime.now().strftime("%Y-%m-%d")
    return int(args.func(args))


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