# -*- coding: utf-8 -*-
"""执行与队列 API

提供 8 个端点：
- POST /api/execution/run          — 直接执行任务
- GET  /api/execution/queue        — 获取当前队列（按 site_id 过滤）
- POST /api/execution/queue        — 添加到队列
- PUT  /api/execution/queue/reorder — 重排队列
- DELETE /api/execution/queue/{id} — 删除队列任务
- POST /api/execution/{id}/cancel  — 取消执行中的任务
- GET  /api/execution/history      — 执行历史（按 site_id 过滤）
- GET  /api/execution/{id}/logs    — 获取历史日志

所有端点需要 JWT 认证，site_id 从 JWT 提取。
"""

from __future__ import annotations

import asyncio
import json
import logging
import uuid

from fastapi import APIRouter, Depends, HTTPException, Query, status

from app.auth.dependencies import CurrentUser, get_current_user
from app.database import get_connection
from app.schemas.execution import (
    ExecutionHistoryItem,
    ExecutionLogsResponse,
    ExecutionRunResponse,
    QueueTaskResponse,
    ReorderRequest,
)
from app.schemas.tasks import TaskConfigSchema
from app.services.task_executor import task_executor
from app.services.task_queue import task_queue
from app.services.output_cleanup import cleanup_output_dirs

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/execution", tags=["任务执行"])


# ── POST /api/execution/run — 直接执行任务 ────────────────────

@router.post("/run", response_model=ExecutionRunResponse)
async def run_task(
    config: TaskConfigSchema,
    user: CurrentUser = Depends(get_current_user),
) -> ExecutionRunResponse:
    """直接执行任务（不经过队列）。

    从 JWT 注入 store_id，创建 execution_id 后异步启动子进程。
    """
    config = config.model_copy(update={"store_id": user.site_id})
    execution_id = str(uuid.uuid4())

    # 异步启动执行，不阻塞响应
    asyncio.create_task(
        task_executor.execute(
            config=config,
            execution_id=execution_id,
            site_id=user.site_id,
        )
    )

    return ExecutionRunResponse(
        execution_id=execution_id,
        message="任务已提交执行",
    )


# ── GET /api/execution/queue — 获取当前队列 ───────────────────

@router.get("/queue", response_model=list[QueueTaskResponse])
async def get_queue(
    user: CurrentUser = Depends(get_current_user),
) -> list[QueueTaskResponse]:
    """获取当前门店的待执行队列。"""
    tasks = task_queue.list_pending(user.site_id)
    return [
        QueueTaskResponse(
            id=t.id,
            site_id=t.site_id,
            config=t.config,
            status=t.status,
            position=t.position,
            created_at=t.created_at,
            started_at=t.started_at,
            finished_at=t.finished_at,
            exit_code=t.exit_code,
            error_message=t.error_message,
        )
        for t in tasks
    ]


# ── POST /api/execution/queue — 添加到队列 ───────────────────

@router.post("/queue", response_model=QueueTaskResponse, status_code=status.HTTP_201_CREATED)
async def enqueue_task(
    config: TaskConfigSchema,
    user: CurrentUser = Depends(get_current_user),
) -> QueueTaskResponse:
    """将任务配置添加到执行队列。"""
    config = config.model_copy(update={"store_id": user.site_id})
    task_id = task_queue.enqueue(config, user.site_id)

    # 查询刚创建的任务返回完整信息
    conn = get_connection()
    try:
        with conn.cursor() as cur:
            cur.execute(
                """
                SELECT id, site_id, config, status, position,
                       created_at, started_at, finished_at,
                       exit_code, error_message
                FROM task_queue WHERE id = %s
                """,
                (task_id,),
            )
            row = cur.fetchone()
        conn.commit()
    finally:
        conn.close()

    if row is None:
        raise HTTPException(status_code=500, detail="入队后查询失败")

    config_data = row[2] if isinstance(row[2], dict) else json.loads(row[2])
    return QueueTaskResponse(
        id=str(row[0]),
        site_id=row[1],
        config=config_data,
        status=row[3],
        position=row[4],
        created_at=row[5],
        started_at=row[6],
        finished_at=row[7],
        exit_code=row[8],
        error_message=row[9],
    )


# ── PUT /api/execution/queue/reorder — 重排队列 ──────────────

@router.put("/queue/reorder")
async def reorder_queue(
    body: ReorderRequest,
    user: CurrentUser = Depends(get_current_user),
) -> dict:
    """调整队列中任务的执行顺序。"""
    task_queue.reorder(body.task_id, body.new_position, user.site_id)
    return {"message": "队列已重排"}


# ── DELETE /api/execution/queue/{id} — 删除队列任务 ──────────

@router.delete("/queue/{task_id}")
async def delete_queue_task(
    task_id: str,
    user: CurrentUser = Depends(get_current_user),
) -> dict:
    """从队列中删除待执行任务。仅允许删除 pending 状态的任务。"""
    deleted = task_queue.delete(task_id, user.site_id)
    if not deleted:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="任务不存在或非待执行状态，无法删除",
        )
    return {"message": "任务已从队列中删除"}


# ── POST /api/execution/{id}/cancel — 取消执行 ──────────────

@router.post("/{execution_id}/cancel")
async def cancel_execution(
    execution_id: str,
    user: CurrentUser = Depends(get_current_user),
) -> dict:
    """取消正在执行的任务。"""
    cancelled = await task_executor.cancel(execution_id)
    if not cancelled:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="执行任务不存在或已完成",
        )
    return {"message": "已发送取消信号"}


import re as _re


def _parse_config_from_command(
    task_codes: list[str],
    command: str | None,
    site_id: int,
) -> TaskConfigSchema:
    """从旧记录的 command 字符串解析出原始 CLI 参数，构建 TaskConfigSchema。

    旧记录没有 config JSONB 列，但 command 包含完整的 CLI 参数。
    """
    kwargs: dict = {
        "tasks": task_codes or [],
        "store_id": site_id,
    }

    if command:
        # 解析 --flow
        m = _re.search(r"--flow\s+(\S+)", command)
        if m:
            kwargs["flow"] = m.group(1)

        # 解析 --processing-mode
        m = _re.search(r"--processing-mode\s+(\S+)", command)
        if m:
            kwargs["processing_mode"] = m.group(1)

        # 解析 --lookback-hours
        m = _re.search(r"--lookback-hours\s+(\d+)", command)
        if m:
            kwargs["lookback_hours"] = int(m.group(1))

        # 解析 --overlap-seconds
        m = _re.search(r"--overlap-seconds\s+(\d+)", command)
        if m:
            kwargs["overlap_seconds"] = int(m.group(1))

        # 解析 --window-start / --window-end
        m = _re.search(r"--window-start\s+(\S+)", command)
        if m:
            kwargs["window_start"] = m.group(1)
            kwargs["window_mode"] = "custom"
        m = _re.search(r"--window-end\s+(\S+)", command)
        if m:
            kwargs["window_end"] = m.group(1)

        # 解析 --dry-run
        if "--dry-run" in command:
            kwargs["dry_run"] = True

        # 解析 --force-full
        if "--force-full" in command:
            kwargs["force_full"] = True

        # 解析 --fetch-before-verify
        if "--fetch-before-verify" in command:
            kwargs["fetch_before_verify"] = True

    return TaskConfigSchema(**kwargs)


# ── POST /api/execution/{id}/rerun — 重新执行 ───────────────

# CHANGE 2026-03-22 | 支持对任意历史任务重新执行
@router.post("/{execution_id}/rerun", response_model=ExecutionRunResponse)
async def rerun_execution(
    execution_id: str,
    user: CurrentUser = Depends(get_current_user),
) -> ExecutionRunResponse:
    """根据历史执行记录重新执行相同的任务。

    优先从 config JSONB 列还原完整配置；若旧记录无 config 列，
    回退到 task_codes + 默认配置。
    """
    conn = get_connection()
    try:
        with conn.cursor() as cur:
            cur.execute(
                """
                SELECT task_codes, site_id, config, command
                FROM task_execution_log
                WHERE id = %s AND site_id = %s
                """,
                (execution_id, user.site_id),
            )
            row = cur.fetchone()
        conn.commit()
    finally:
        conn.close()

    if row is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="执行记录不存在",
        )

    task_codes = row[0] or []
    config_json = row[2]  # JSONB，可能为 None（旧记录）
    command_str = row[3]  # command 字符串，用于旧记录回退解析

    if not task_codes and not config_json:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="原执行记录无任务代码，无法重新执行",
        )

    # CHANGE 2026-03-22 | 优先从存储的完整 config 还原，保留原始 processing_mode/lookback 等参数
    if config_json and isinstance(config_json, dict):
        # 覆盖 store_id 为当前用户的（安全）
        config_json["store_id"] = user.site_id
        config = TaskConfigSchema(**config_json)
    else:
        # 旧记录无 config 列，尝试从 command 字符串解析原始参数
        config = _parse_config_from_command(task_codes, command_str, user.site_id)

    new_execution_id = str(uuid.uuid4())
    asyncio.create_task(
        task_executor.execute(
            config=config,
            execution_id=new_execution_id,
            site_id=user.site_id,
        )
    )

    logger.info(
        "重新执行 [%s] → [%s], tasks=%s",
        execution_id, new_execution_id, task_codes,
    )

    return ExecutionRunResponse(
        execution_id=new_execution_id,
        message=f"已基于 {execution_id[:8]}… 重新执行",
    )


# ── GET /api/execution/history — 执行历史 ────────────────────

@router.get("/history", response_model=list[ExecutionHistoryItem])
async def get_execution_history(
    limit: int = Query(default=50, ge=1, le=200),
    user: CurrentUser = Depends(get_current_user),
) -> list[ExecutionHistoryItem]:
    """获取执行历史记录，按 started_at 降序排列。"""
    conn = get_connection()
    try:
        with conn.cursor() as cur:
            cur.execute(
                """
                SELECT id, site_id, task_codes, status, started_at,
                       finished_at, exit_code, duration_ms, command, summary,
                       schedule_id
                FROM task_execution_log
                WHERE site_id = %s
                ORDER BY started_at DESC
                LIMIT %s
                """,
                (user.site_id, limit),
            )
            rows = cur.fetchall()
        conn.commit()
    finally:
        conn.close()

    return [
        ExecutionHistoryItem(
            id=str(row[0]),
            site_id=row[1],
            task_codes=row[2] or [],
            status=row[3],
            started_at=row[4],
            finished_at=row[5],
            exit_code=row[6],
            duration_ms=row[7],
            command=row[8],
            summary=row[9],
            schedule_id=str(row[10]) if row[10] else None,
        )
        for row in rows
    ]


# ── GET /api/execution/{id}/logs — 获取历史日志 ──────────────

@router.get("/{execution_id}/logs", response_model=ExecutionLogsResponse)
async def get_execution_logs(
    execution_id: str,
    user: CurrentUser = Depends(get_current_user),
) -> ExecutionLogsResponse:
    """获取指定执行的完整日志。

    优先从内存缓冲区读取（执行中），否则从数据库读取（已完成）。
    """
    # 先尝试内存缓冲区（执行中的任务）
    if task_executor.is_running(execution_id):
        lines = task_executor.get_logs(execution_id)
        return ExecutionLogsResponse(
            execution_id=execution_id,
            output_log="\n".join(lines) if lines else None,
        )

    # 从数据库读取已完成任务的日志
    conn = get_connection()
    try:
        with conn.cursor() as cur:
            cur.execute(
                """
                SELECT output_log, error_log
                FROM task_execution_log
                WHERE id = %s AND site_id = %s
                """,
                (execution_id, user.site_id),
            )
            row = cur.fetchone()
        conn.commit()
    finally:
        conn.close()

    if row is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="执行记录不存在",
        )

    return ExecutionLogsResponse(
        execution_id=execution_id,
        output_log=row[0],
        error_log=row[1],
    )


# ── POST /api/execution/cleanup-output — 清理输出目录 ────────

# CHANGE 2026-03-27 | 新增：执行前清理 EXPORT_ROOT 下旧运行记录，每类任务只保留最近 10 个
@router.post("/cleanup-output")
async def cleanup_output(
    user: CurrentUser = Depends(get_current_user),
) -> dict:
    """清理 EXPORT_ROOT 下每个任务文件夹的旧运行记录，只保留最近 10 个。"""
    try:
        result = cleanup_output_dirs(keep=10)
    except RuntimeError as exc:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=str(exc),
        )
    return result
