# -*- coding: utf-8 -*-
"""
schedule_service.py — 定时任务管理（CRUD + APScheduler 集成）

数据存储：service_monitor/data/schedules.json
调度器：APScheduler BackgroundScheduler
"""

from __future__ import annotations

import json
import logging
import threading
import uuid
from datetime import datetime
from typing import Optional

from ..utils.paths import SCHEDULES_FILE, ensure_dirs

logger = logging.getLogger("service_monitor.schedule")

# 全局调度器（延迟初始化）
_scheduler = None
_scheduler_lock = threading.Lock()


def _load_schedules() -> list:
    """加载定时任务列表。"""
    if not SCHEDULES_FILE.exists():
        return []
    try:
        data = json.loads(SCHEDULES_FILE.read_text(encoding="utf-8"))
        return data if isinstance(data, list) else []
    except (json.JSONDecodeError, OSError) as e:
        logger.error("加载 schedules.json 失败: %s", e)
        return []


def _save_schedules(schedules: list) -> None:
    """保存定时任务列表。"""
    ensure_dirs()
    SCHEDULES_FILE.write_text(
        json.dumps(schedules, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )


def _now() -> str:
    return datetime.now().strftime("%Y-%m-%dT%H:%M:%S")


def _calc_next_run(cron_expr: str) -> Optional[str]:
    """计算下次执行时间。"""
    try:
        from croniter import croniter
        base = datetime.now()
        iter = croniter(cron_expr, base)
        next_dt = iter.get_next(datetime)
        return next_dt.strftime("%Y-%m-%dT%H:%M:%S")
    except Exception as e:
        logger.warning("计算下次执行时间失败: %s", e)
        return None


# ============================================================
# CRUD
# ============================================================

def list_schedules() -> list:
    """列出所有定时任务。"""
    return _load_schedules()


def get_schedule(schedule_id: str) -> Optional[dict]:
    """获取单个定时任务。"""
    for s in _load_schedules():
        if s.get("id") == schedule_id:
            return s
    return None


def create_schedule(data: dict, created_by: str) -> dict:
    """创建定时任务。"""
    # 校验必填字段
    name = data.get("name", "").strip()
    target_id = data.get("target_id", "").strip()
    suite = data.get("suite", "quick")
    cron_expr = data.get("cron", "").strip()

    if not name:
        raise ValueError("任务名称不能为空")
    if not target_id:
        raise ValueError("请选择监测目标")
    if not cron_expr:
        raise ValueError("请输入 Cron 表达式")

    # 校验 cron 表达式
    try:
        from croniter import croniter
        croniter(cron_expr)  # 不抛异常即合法
    except Exception as e:
        raise ValueError(f"Cron 表达式无效: {e}")

    # 生成 ID
    schedule_id = "sched_" + str(uuid.uuid4())[:12]

    # 获取目标名称
    from . import target_service
    target = target_service.get_target(target_id)
    target_name = target.get("name") if target else target_id

    schedule = {
        "id": schedule_id,
        "name": name,
        "target_id": target_id,
        "target_name": target_name,
        "suite": suite,
        "cron": cron_expr,
        "enabled": True,
        "last_run_at": None,
        "last_run_status": None,
        "last_report_id": None,
        "next_run_at": _calc_next_run(cron_expr),
        "created_at": _now(),
        "created_by": created_by,
    }

    schedules = _load_schedules()
    schedules.append(schedule)
    _save_schedules(schedules)

    logger.info("创建定时任务: %s (%s)", name, cron_expr)
    return schedule


def update_schedule(schedule_id: str, data: dict) -> dict:
    """更新定时任务。"""
    schedules = _load_schedules()
    for i, s in enumerate(schedules):
        if s.get("id") == schedule_id:
            # 可更新字段
            if "name" in data:
                name = data["name"].strip()
                if not name:
                    raise ValueError("任务名称不能为空")
                schedules[i]["name"] = name
            if "cron" in data:
                cron_expr = data["cron"].strip()
                if not cron_expr:
                    raise ValueError("Cron 表达式不能为空")
                try:
                    from croniter import croniter
                    croniter(cron_expr)
                except Exception as e:
                    raise ValueError(f"Cron 表达式无效: {e}")
                schedules[i]["cron"] = cron_expr
                schedules[i]["next_run_at"] = _calc_next_run(cron_expr)
            if "suite" in data:
                schedules[i]["suite"] = data["suite"]

            _save_schedules(schedules)
            logger.info("更新定时任务: %s", schedule_id)
            return schedules[i]

    raise ValueError("定时任务不存在")


def delete_schedule(schedule_id: str) -> bool:
    """删除定时任务。"""
    schedules = _load_schedules()
    new_schedules = [s for s in schedules if s.get("id") != schedule_id]
    if len(new_schedules) == len(schedules):
        return False
    _save_schedules(new_schedules)
    logger.info("删除定时任务: %s", schedule_id)
    return True


def toggle_enabled(schedule_id: str, enabled: Optional[bool] = None) -> dict:
    """切换定时任务启用/禁用状态。"""
    schedules = _load_schedules()
    for i, s in enumerate(schedules):
        if s.get("id") == schedule_id:
            new_enabled = not s.get("enabled", True) if enabled is None else enabled
            schedules[i]["enabled"] = new_enabled
            if new_enabled:
                schedules[i]["next_run_at"] = _calc_next_run(s["cron"])
            _save_schedules(schedules)
            logger.info("切换定时任务状态: %s -> %s", schedule_id, new_enabled)
            return schedules[i]
    raise ValueError("定时任务不存在")


def update_run_status(schedule_id: str, status: str, report_id: str, run_at: str) -> dict:
    """更新定时任务执行状态。"""
    schedules = _load_schedules()
    for i, s in enumerate(schedules):
        if s.get("id") == schedule_id:
            schedules[i]["last_run_at"] = run_at
            schedules[i]["last_run_status"] = status
            schedules[i]["last_report_id"] = report_id
            schedules[i]["next_run_at"] = _calc_next_run(s["cron"])
            _save_schedules(schedules)
            return schedules[i]
    raise ValueError("定时任务不存在")


# ============================================================
# APScheduler 集成
# ============================================================

def init_scheduler(app) -> None:
    """初始化 APScheduler，在 Flask app 创建后调用。"""
    global _scheduler
    with _scheduler_lock:
        if _scheduler is not None:
            return

        try:
            from apscheduler.schedulers.background import BackgroundScheduler
            _scheduler = BackgroundScheduler()
            _scheduler.start()
            logger.info("APScheduler 已启动")

            # 加载已启用的定时任务
            _reload_jobs()
        except ImportError:
            logger.warning("APScheduler 未安装，定时任务功能不可用")


def _reload_jobs() -> None:
    """从 schedules.json 重新加载所有任务。"""
    if _scheduler is None:
        return

    schedules = list_schedules()
    for s in schedules:
        if s.get("enabled"):
            _add_job(s)
        else:
            _remove_job(s["id"])


def _add_job(sched: dict) -> None:
    """添加或更新单个 APScheduler job。"""
    if _scheduler is None:
        return

    from apscheduler.triggers.cron import CronTrigger

    job_id = f"schedule_{sched['id']}"
    try:
        trigger = CronTrigger.from_crontab(sched["cron"])
        _scheduler.add_job(
            func=_execute_scheduled_job,
            trigger=trigger,
            id=job_id,
            args=[sched["id"]],
            replace_existing=True,
        )
        logger.info("定时任务已注册: %s (%s)", sched["name"], sched["cron"])
    except Exception as e:
        logger.error("注册定时任务失败: %s - %s", sched["id"], e)


def _remove_job(schedule_id: str) -> None:
    """移除 APScheduler job。"""
    if _scheduler is None:
        return

    job_id = f"schedule_{schedule_id}"
    try:
        _scheduler.remove_job(job_id)
        logger.info("定时任务已移除: %s", schedule_id)
    except Exception:
        pass  # job 可能不存在


def _execute_scheduled_job(schedule_id: str) -> None:
    """定时任务执行入口（在 APScheduler 线程中调用）。"""
    logger.info("定时任务触发: %s", schedule_id)

    sched = get_schedule(schedule_id)
    if not sched or not sched.get("enabled"):
        logger.warning("定时任务不存在或已禁用: %s", schedule_id)
        return

    try:
        from . import runner_service
        result = runner_service.run_inspection_sync(
            sched["target_id"], sched["suite"]
        )
        update_run_status(
            schedule_id,
            status="success" if result.get("success") else "failed",
            report_id=result.get("report_id", ""),
            run_at=datetime.now().strftime("%Y-%m-%dT%H:%M:%S"),
        )
        logger.info("定时任务执行完成: %s, report_id=%s", schedule_id, result.get("report_id"))
    except Exception as e:
        logger.error("定时任务执行异常: %s - %s", schedule_id, e)
        update_run_status(
            schedule_id,
            status="error",
            report_id="",
            run_at=datetime.now().strftime("%Y-%m-%dT%H:%M:%S"),
        )


def reload_job(schedule_id: str) -> None:
    """重新加载单个定时任务（CRUD 后调用）。"""
    if _scheduler is None:
        return

    sched = get_schedule(schedule_id)
    if sched and sched.get("enabled"):
        _add_job(sched)
    else:
        _remove_job(schedule_id)


def get_scheduler_status() -> dict:
    """获取调度器状态。"""
    if _scheduler is None:
        return {"running": False, "reason": "APScheduler 未初始化"}
    return {
        "running": _scheduler.running,
        "job_count": len(_scheduler.get_jobs()),
    }
