#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：scheduled_tasks.py
模块描述：UI 自动化定时任务管理 API 路由

作者：czj
创建日期：2026-08-19
最后修改：2026-08-19
"""

import asyncio
import logging
from typing import Optional, List

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select, desc, or_
from sqlalchemy.ext.asyncio import AsyncSession

from app.database import get_db
from app.models.scheduled_task import ScheduledTask
from app.models.module import Module
from app.models.execution import Execution
from app.schemas.scheduled_task import (
    ScheduledTaskCreate,
    ScheduledTaskUpdate,
    ToggleRequest,
)
from app.services.scheduler_service import compute_next_run, run_task_once
from app.utils.id_generator import generate_id

logger = logging.getLogger(__name__)

router = APIRouter()


async def _module_name_map(db: AsyncSession, module_ids: List[str]) -> dict:
    """查询模块 ID → 名称映射"""
    if not module_ids:
        return {}
    result = await db.execute(select(Module).where(Module.id.in_(module_ids)))
    return {m.id: m.name for m in result.scalars().all()}


async def _task_to_dict(db: AsyncSession, task: ScheduledTask) -> dict:
    """任务字典 + 模块名称列表 + 周期描述"""
    data = task.to_dict()
    name_map = await _module_name_map(db, task.module_ids or [])
    data["module_names"] = [
        name_map.get(mid, mid) for mid in (task.module_ids or [])
    ]
    data["schedule_description"] = task.schedule_description()
    return data


@router.get("", response_model=dict)
async def list_tasks(
    enabled: Optional[bool] = None,
    db: AsyncSession = Depends(get_db),
):
    """
    获取定时任务列表

    Args:
        enabled (Optional[bool]): 按启用状态筛选
        db (AsyncSession): 数据库会话

    Returns:
        dict: 任务列表
    """
    try:
        query = select(ScheduledTask).order_by(
            desc(ScheduledTask.enabled),
            ScheduledTask.created_at.desc(),
        )
        if enabled is not None:
            query = query.where(ScheduledTask.enabled == enabled)
        result = await db.execute(query)
        tasks = list(result.scalars().all())

        items = []
        for task in tasks:
            items.append(await _task_to_dict(db, task))

        return {"total": len(items), "items": items}
    except Exception as e:
        logger.error(f"获取定时任务列表失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")


@router.post("", response_model=dict, status_code=201)
async def create_task(
    data: ScheduledTaskCreate,
    db: AsyncSession = Depends(get_db),
):
    """
    创建定时任务

    Args:
        data (ScheduledTaskCreate): 创建数据
        db (AsyncSession): 数据库会话

    Returns:
        dict: 任务数据
    """
    try:
        task = ScheduledTask(
            id=generate_id("scheduled"),
            name=data.name.strip(),
            enabled=data.enabled,
            module_ids=data.module_ids or [],
            schedule_type=data.schedule_type,
            interval_value=data.interval_value,
            interval_unit=data.interval_unit,
            run_time=data.run_time or "",
            weekdays=data.weekdays or [],
            auto_report=data.auto_report,
        )
        task.next_run_at = compute_next_run(task)
        db.add(task)
        await db.flush()
        await db.refresh(task)
        return await _task_to_dict(db, task)
    except Exception as e:
        logger.error(f"创建定时任务失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")


@router.put("/{task_id}", response_model=dict)
async def update_task(
    task_id: str,
    data: ScheduledTaskUpdate,
    db: AsyncSession = Depends(get_db),
):
    """
    更新定时任务

    只更新传入字段，更新后重算下次触发时间。

    Args:
        task_id (str): 任务ID
        data (ScheduledTaskUpdate): 更新数据
        db (AsyncSession): 数据库会话

    Returns:
        dict: 任务数据
    """
    try:
        result = await db.execute(
            select(ScheduledTask).where(ScheduledTask.id == task_id)
        )
        task = result.scalar_one_or_none()
        if not task:
            raise HTTPException(status_code=404, detail=f"定时任务不存在: {task_id}")

        update_data = data.model_dump(exclude_unset=True)
        for field, value in update_data.items():
            setattr(task, field, value)

        task.name = (task.name or "").strip()
        task.next_run_at = compute_next_run(task)
        await db.flush()
        await db.refresh(task)
        return await _task_to_dict(db, task)
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"更新定时任务失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")


@router.delete("/{task_id}", status_code=204)
async def delete_task(
    task_id: str,
    db: AsyncSession = Depends(get_db),
):
    """
    删除定时任务（不影响已产生的执行记录与报告）

    Args:
        task_id (str): 任务ID
        db (AsyncSession): 数据库会话
    """
    try:
        result = await db.execute(
            select(ScheduledTask).where(ScheduledTask.id == task_id)
        )
        task = result.scalar_one_or_none()
        if not task:
            raise HTTPException(status_code=404, detail=f"定时任务不存在: {task_id}")

        await db.delete(task)
        await db.flush()
        logger.info(f"定时任务已删除: {task_id}")
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"删除定时任务失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")


@router.post("/{task_id}/toggle", response_model=dict)
async def toggle_task(
    task_id: str,
    data: ToggleRequest,
    db: AsyncSession = Depends(get_db),
):
    """
    启用 / 停用定时任务

    启用时计算下次触发时间；停用时保留下次触发时间（再次启用后重算）。

    Args:
        task_id (str): 任务ID
        data (ToggleRequest): {enabled: bool}
        db (AsyncSession): 数据库会话

    Returns:
        dict: 任务数据
    """
    try:
        result = await db.execute(
            select(ScheduledTask).where(ScheduledTask.id == task_id)
        )
        task = result.scalar_one_or_none()
        if not task:
            raise HTTPException(status_code=404, detail=f"定时任务不存在: {task_id}")

        task.enabled = data.enabled
        if data.enabled:
            task.next_run_at = compute_next_run(task)
        await db.flush()
        await db.refresh(task)
        return await _task_to_dict(db, task)
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"切换定时任务状态失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")


@router.post("/{task_id}/run", response_model=dict)
async def run_task_now(
    task_id: str,
    db: AsyncSession = Depends(get_db),
):
    """
    立即执行定时任务（手动触发一次）

    后台异步创建执行记录并运行，执行完成后自动生成报告。

    若当前已有 UI 执行任务在运行，返回跳过状态（不创建执行记录），
    前端可据此提示用户"请等待当前执行完成"。

    Args:
        task_id (str): 任务ID
        db (AsyncSession): 数据库会话

    Returns:
        dict: 触发结果，含 status 字段（triggered / skipped）
    """
    try:
        result = await db.execute(
            select(ScheduledTask).where(ScheduledTask.id == task_id)
        )
        task = result.scalar_one_or_none()
        if not task:
            raise HTTPException(status_code=404, detail=f"定时任务不存在: {task_id}")

        # 预检全局执行锁：已有 UI 执行在运行 → 返回明确跳过状态
        from app.services.execution_service import is_execution_running

        is_running, running_id = is_execution_running()
        if is_running:
            logger.info(
                f"[定时任务] 手动触发跳过：已有执行 {running_id} 正在运行"
            )
            return {
                "message": f"已有执行 {running_id} 正在运行，请等待完成后再执行",
                "task_id": task_id,
                "status": "skipped",
                "running_execution_id": running_id,
            }

        # 后台异步执行（manual=True 绕过任务级去重守卫）
        asyncio.create_task(run_task_once(task_id, manual=True))
        return {
            "message": f"任务「{task.name}」已触发执行",
            "task_id": task_id,
            "status": "triggered",
        }
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"立即执行定时任务失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")


@router.get("/{task_id}/runs", response_model=dict)
async def list_task_runs(
    task_id: str,
    skip: int = 0,
    limit: int = 20,
    db: AsyncSession = Depends(get_db),
):
    """
    获取定时任务运行历史

    查询该任务触发的执行记录（trigger_type=scheduled 且 trigger_by=任务名）。

    Args:
        task_id (str): 任务ID
        skip (int): 跳过记录数
        limit (int): 返回记录数上限
        db (AsyncSession): 数据库会话

    Returns:
        dict: 运行历史列表
    """
    try:
        result = await db.execute(
            select(ScheduledTask).where(ScheduledTask.id == task_id)
        )
        task = result.scalar_one_or_none()
        if not task:
            raise HTTPException(status_code=404, detail=f"定时任务不存在: {task_id}")

        query = (
            select(Execution)
            .where(
                Execution.trigger_type == "scheduled",
                Execution.trigger_by == task.name,
            )
            .order_by(desc(Execution.created_at))
            .offset(skip)
            .limit(limit)
        )
        result = await db.execute(query)
        executions = list(result.scalars().all())

        return {
            "total": len(executions),
            "items": [e.to_dict() for e in executions],
        }
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"获取定时任务运行历史失败: {str(e)}")
        raise HTTPException(status_code=500, detail=f"服务器内部错误: {str(e)}")
