#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：security_scheduled_tasks.py
模块描述：安全测试定时任务管理 API 路由

作者：czj
创建日期：2026-08-19
"""

import asyncio
import logging
from typing import Optional, List

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

from app.database import get_db
from app.models.scheduled_task import ScheduledTask
from app.models.security_config import SecurityConfig
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 _config_name_map(db: AsyncSession) -> dict:
    """查询安全测试配置 ID → 名称映射"""
    result = await db.execute(select(SecurityConfig))
    return {c.id: c.name for c 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 [])
    ]
    if task.config_id:
        cfg_map = await _config_name_map(db)
        data["config_name"] = cfg_map.get(task.config_id, task.config_id)
    else:
        data["config_name"] = None
    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),
):
    """
    获取安全测试定时任务列表

    筛选 case_type="security" 的安全测试定时任务。
    """
    try:
        query = (
            select(ScheduledTask)
            .where(ScheduledTask.case_type == "security")
            .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),
):
    """
    创建安全测试定时任务

    自动设 case_type="security"，校验 config_id 必填。
    """
    try:
        # 校验 config_id 必填
        if not data.config_id:
            raise HTTPException(
                status_code=422, detail="安全测试定时任务必须指定测试配置 (config_id)"
            )

        # 校验 config_id 是否存在
        cfg_result = await db.execute(
            select(SecurityConfig).where(SecurityConfig.id == data.config_id)
        )
        if not cfg_result.scalar_one_or_none():
            raise HTTPException(
                status_code=404,
                detail=f"安全测试配置不存在: {data.config_id}",
            )

        task = ScheduledTask(
            id=generate_id("scheduled"),
            name=data.name.strip(),
            case_type="security",
            config_id=data.config_id,
            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 HTTPException:
        raise
    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),
):
    """
    更新安全测试定时任务

    不允许修改 case_type。
    """
    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}")

        # 不允许修改 case_type
        if data.case_type is not None and data.case_type != task.case_type:
            raise HTTPException(
                status_code=422, detail="不允许修改定时任务类型 (case_type)"
            )

        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),
):
    """
    删除安全测试定时任务（不影响已产生的执行记录与报告）
    """
    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),
):
    """
    启用 / 停用安全测试定时任务
    """
    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),
):
    """
    立即执行安全测试定时任务（手动触发一次）

    后台异步创建执行记录并运行，执行完成后自动生成报告。
    """
    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}")

        # 后台异步执行（不阻塞请求）
        asyncio.create_task(run_task_once(task_id))
        return {
            "message": f"安全测试任务「{task.name}」已触发执行",
            "task_id": task_id,
        }
    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=任务名）。
    """
    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,
                Execution.case_type == "security",
            )
            .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)}")