# -*- coding: utf-8 -*-
"""任务管理器面板"""

import uuid
from datetime import datetime, timedelta
from typing import Dict, List, Optional

from PySide6.QtWidgets import (
    QWidget, QVBoxLayout, QHBoxLayout, QSplitter, QGridLayout,
    QGroupBox, QLabel, QPushButton, QTableWidget, QTableWidgetItem,
    QHeaderView, QMessageBox, QMenu, QAbstractItemView, QDialog,
    QComboBox, QSpinBox, QLineEdit, QCheckBox, QTimeEdit, QDateEdit,
    QListWidget, QListWidgetItem, QDialogButtonBox, QTabWidget, QFrame,
    QTextEdit, QPlainTextEdit
)
from PySide6.QtCore import Qt, Signal, QTimer, QTime, QDate
from PySide6.QtGui import QColor, QAction, QFont

from ..models.task_model import QueuedTask, TaskConfig, TaskStatus
from ..models.schedule_model import (
    ScheduledTask, ScheduleConfig, ScheduleType, IntervalUnit, ScheduleStore,
    ScheduleExecutionRecord
)
from ..utils.cli_builder import CLIBuilder
from ..utils.app_settings import app_settings
from ..workers.task_worker import TaskWorker


# 动态获取可调度的任务列表
def _get_schedulable_tasks():
    """从任务注册表动态获取可调度任务列表"""
    try:
        from ..models.task_registry import task_registry
        tasks = []
        # 添加所有 ODS 任务
        for task_def in task_registry.get_ods_tasks():
            tasks.append((task_def.code, task_def.name))
        # 添加非 ODS 任务（排除 Schema 初始化和手工灌入）
        exclude_codes = {"INIT_ODS_SCHEMA", "INIT_DWD_SCHEMA", "INIT_DWS_SCHEMA", "MANUAL_INGEST"}
        for task_def in task_registry.get_non_ods_tasks():
            if task_def.code not in exclude_codes:
                tasks.append((task_def.code, task_def.name))
        return tasks
    except ImportError:
        # 回退到静态列表
        return [
            ("ODS_PAYMENT", "支付流水"),
            ("ODS_MEMBER", "会员档案"),
            ("ODS_MEMBER_CARD", "会员储值卡"),
            ("ODS_MEMBER_BALANCE", "会员余额变动"),
            ("ODS_SETTLEMENT_RECORDS", "结账记录"),
            ("ODS_TABLE_USE", "台费计费流水"),
            ("ODS_ASSISTANT_ACCOUNT", "助教账号"),
            ("ODS_ASSISTANT_LEDGER", "助教流水"),
            ("ODS_ASSISTANT_ABOLISH", "助教作废"),
            ("ODS_REFUND", "退款流水"),
            ("ODS_PLATFORM_COUPON", "平台券核销"),
            ("ODS_RECHARGE_SETTLE", "充值结算"),
            ("ODS_SETTLEMENT_TICKET", "结账小票"),
            ("DWD_LOAD_FROM_ODS", "ODS→DWD 装载"),
            ("DWD_QUALITY_CHECK", "DWD 质量检查"),
            ("DATA_INTEGRITY_CHECK", "数据完整性检查"),
            ("CHECK_CUTOFF", "检查 Cutoff"),
        ]


SCHEDULABLE_TASKS = _get_schedulable_tasks()


class TaskLogDialog(QDialog):
    """任务日志查看对话框"""
    
    def __init__(self, task: QueuedTask, parent=None):
        super().__init__(parent)
        self.task = task
        self.setWindowTitle(f"任务日志 - {', '.join(task.config.tasks[:2])}")
        self.setMinimumSize(800, 600)
        self._init_ui()
    
    def _init_ui(self):
        layout = QVBoxLayout(self)
        
        # 任务信息
        info_group = QGroupBox("任务信息")
        info_layout = QGridLayout(info_group)
        
        info_layout.addWidget(QLabel("任务 ID:"), 0, 0)
        info_layout.addWidget(QLabel(self.task.id), 0, 1)
        
        info_layout.addWidget(QLabel("任务列表:"), 0, 2)
        info_layout.addWidget(QLabel(", ".join(self.task.config.tasks)), 0, 3)
        
        info_layout.addWidget(QLabel("状态:"), 1, 0)
        status_label = QLabel(self._get_status_text(self.task.status))
        status_label.setStyleSheet(f"color: {self._get_status_color(self.task.status)};")
        info_layout.addWidget(status_label, 1, 1)
        
        info_layout.addWidget(QLabel("退出码:"), 1, 2)
        info_layout.addWidget(QLabel(str(self.task.exit_code) if self.task.exit_code is not None else "-"), 1, 3)
        
        if self.task.started_at:
            info_layout.addWidget(QLabel("开始时间:"), 2, 0)
            info_layout.addWidget(QLabel(self.task.started_at.strftime("%Y-%m-%d %H:%M:%S")), 2, 1)
        
        if self.task.finished_at:
            info_layout.addWidget(QLabel("结束时间:"), 2, 2)
            info_layout.addWidget(QLabel(self.task.finished_at.strftime("%Y-%m-%d %H:%M:%S")), 2, 3)
            
            if self.task.started_at:
                duration = (self.task.finished_at - self.task.started_at).total_seconds()
                info_layout.addWidget(QLabel("耗时:"), 3, 0)
                info_layout.addWidget(QLabel(f"{duration:.1f} 秒"), 3, 1)
        
        layout.addWidget(info_group)
        
        # 命令行
        cmd_group = QGroupBox("执行命令")
        cmd_layout = QVBoxLayout(cmd_group)
        cmd_text = QLineEdit()
        cmd_text.setReadOnly(True)
        from ..utils.cli_builder import CLIBuilder
        cli = CLIBuilder()
        cmd_text.setText(cli.build_command_string(self.task.config))
        cmd_layout.addWidget(cmd_text)
        
        # 显示环境变量
        if hasattr(self.task.config, 'env_vars') and self.task.config.env_vars:
            env_label = QLabel("环境变量: " + ", ".join(f"{k}={v}" for k, v in self.task.config.env_vars.items()))
            env_label.setWordWrap(True)
            cmd_layout.addWidget(env_label)
        
        layout.addWidget(cmd_group)
        
        # 输出日志
        log_group = QGroupBox("执行输出")
        log_layout = QVBoxLayout(log_group)
        
        self.log_text = QPlainTextEdit()
        self.log_text.setReadOnly(True)
        self.log_text.setFont(QFont("Consolas", 9))
        self.log_text.setPlainText(self.task.output if self.task.output else "(无输出)")
        log_layout.addWidget(self.log_text)
        
        layout.addWidget(log_group)
        
        # 错误信息
        if self.task.error:
            error_group = QGroupBox("错误信息")
            error_layout = QVBoxLayout(error_group)
            error_text = QPlainTextEdit()
            error_text.setReadOnly(True)
            error_text.setPlainText(self.task.error)
            error_text.setMaximumHeight(100)
            error_layout.addWidget(error_text)
            layout.addWidget(error_group)
        
        # 按钮
        btn_layout = QHBoxLayout()
        
        copy_btn = QPushButton("复制日志")
        copy_btn.clicked.connect(self._copy_log)
        btn_layout.addWidget(copy_btn)
        
        btn_layout.addStretch()
        
        close_btn = QPushButton("关闭")
        close_btn.clicked.connect(self.accept)
        btn_layout.addWidget(close_btn)
        
        layout.addLayout(btn_layout)
    
    def _copy_log(self):
        """复制日志到剪贴板"""
        from PySide6.QtWidgets import QApplication
        QApplication.clipboard().setText(self.task.output or "")
        QMessageBox.information(self, "提示", "日志已复制到剪贴板")
    
    @staticmethod
    def _get_status_text(status: TaskStatus) -> str:
        return {
            TaskStatus.PENDING: "待执行",
            TaskStatus.RUNNING: "执行中",
            TaskStatus.SUCCESS: "成功",
            TaskStatus.FAILED: "失败",
            TaskStatus.CANCELLED: "已取消",
        }.get(status, "未知")
    
    @staticmethod
    def _get_status_color(status: TaskStatus) -> str:
        return {
            TaskStatus.PENDING: "#5f6368",
            TaskStatus.RUNNING: "#1a73e8",
            TaskStatus.SUCCESS: "#1e8e3e",
            TaskStatus.FAILED: "#d93025",
            TaskStatus.CANCELLED: "#9aa0a6",
        }.get(status, "#333333")


class ScheduleEditDialog(QDialog):
    """调度任务编辑对话框"""
    
    def __init__(self, task: Optional[ScheduledTask] = None, parent=None):
        super().__init__(parent)
        self.task = task
        self.cli_builder = CLIBuilder()
        self.setWindowTitle("编辑调度任务" if task else "新建调度任务")
        self.setMinimumWidth(600)
        self.setMinimumHeight(700)
        self._init_ui()
        self._connect_preview_signals()
        if task:
            self._load_task(task)
        self._update_cli_preview()  # 初始化预览
    
    def _init_ui(self):
        layout = QVBoxLayout(self)
        
        # 基本信息
        basic_group = QGroupBox("基本信息")
        basic_layout = QGridLayout(basic_group)
        
        basic_layout.addWidget(QLabel("任务名称:"), 0, 0)
        self.name_edit = QLineEdit()
        self.name_edit.setPlaceholderText("例: 每日数据更新")
        basic_layout.addWidget(self.name_edit, 0, 1)
        
        basic_layout.addWidget(QLabel("启用:"), 1, 0)
        self.enabled_check = QCheckBox("启用此调度任务")
        self.enabled_check.setChecked(True)
        basic_layout.addWidget(self.enabled_check, 1, 1)
        
        layout.addWidget(basic_group)
        
        # 任务选择
        task_group = QGroupBox("执行任务")
        task_layout = QVBoxLayout(task_group)
        
        self.task_list = QListWidget()
        self.task_list.setSelectionMode(QListWidget.MultiSelection)
        self.task_list.setMaximumHeight(150)
        for code, name in SCHEDULABLE_TASKS:
            item = QListWidgetItem(f"{name} ({code})")
            item.setData(Qt.UserRole, code)
            self.task_list.addItem(item)
        task_layout.addWidget(self.task_list)
        
        layout.addWidget(task_group)
        
        # 调度设置
        schedule_group = QGroupBox("调度设置")
        schedule_layout = QVBoxLayout(schedule_group)
        
        # 调度类型
        type_layout = QHBoxLayout()
        type_layout.addWidget(QLabel("调度类型:"))
        self.schedule_type_combo = QComboBox()
        self.schedule_type_combo.addItem("固定间隔", ScheduleType.INTERVAL)
        self.schedule_type_combo.addItem("每天定时", ScheduleType.DAILY)
        self.schedule_type_combo.addItem("每周定时", ScheduleType.WEEKLY)
        self.schedule_type_combo.addItem("Cron 表达式", ScheduleType.CRON)
        self.schedule_type_combo.currentIndexChanged.connect(self._on_type_changed)
        type_layout.addWidget(self.schedule_type_combo, 1)
        schedule_layout.addLayout(type_layout)
        
        # 间隔设置
        self.interval_widget = QWidget()
        interval_layout = QHBoxLayout(self.interval_widget)
        interval_layout.setContentsMargins(0, 0, 0, 0)
        interval_layout.addWidget(QLabel("执行间隔:"))
        self.interval_value = QSpinBox()
        self.interval_value.setRange(1, 999)
        self.interval_value.setValue(1)
        interval_layout.addWidget(self.interval_value)
        self.interval_unit = QComboBox()
        self.interval_unit.addItem("分钟", IntervalUnit.MINUTES)
        self.interval_unit.addItem("小时", IntervalUnit.HOURS)
        self.interval_unit.addItem("天", IntervalUnit.DAYS)
        self.interval_unit.setCurrentIndex(1)  # 默认小时
        interval_layout.addWidget(self.interval_unit)
        interval_layout.addStretch()
        schedule_layout.addWidget(self.interval_widget)
        
        # 每日设置
        self.daily_widget = QWidget()
        daily_layout = QHBoxLayout(self.daily_widget)
        daily_layout.setContentsMargins(0, 0, 0, 0)
        daily_layout.addWidget(QLabel("执行时间:"))
        self.daily_time = QTimeEdit()
        self.daily_time.setTime(QTime(4, 0))
        self.daily_time.setDisplayFormat("HH:mm")
        daily_layout.addWidget(self.daily_time)
        daily_layout.addStretch()
        self.daily_widget.setVisible(False)
        schedule_layout.addWidget(self.daily_widget)
        
        # 每周设置
        self.weekly_widget = QWidget()
        weekly_layout = QVBoxLayout(self.weekly_widget)
        weekly_layout.setContentsMargins(0, 0, 0, 0)
        
        days_layout = QHBoxLayout()
        days_layout.addWidget(QLabel("执行日:"))
        self.day_checks = {}
        for i, day in enumerate(["一", "二", "三", "四", "五", "六", "日"], 1):
            check = QCheckBox(f"周{day}")
            check.setChecked(i == 1)  # 默认周一
            self.day_checks[i] = check
            days_layout.addWidget(check)
        weekly_layout.addLayout(days_layout)
        
        weekly_time_layout = QHBoxLayout()
        weekly_time_layout.addWidget(QLabel("执行时间:"))
        self.weekly_time = QTimeEdit()
        self.weekly_time.setTime(QTime(4, 0))
        self.weekly_time.setDisplayFormat("HH:mm")
        weekly_time_layout.addWidget(self.weekly_time)
        weekly_time_layout.addStretch()
        weekly_layout.addLayout(weekly_time_layout)
        
        self.weekly_widget.setVisible(False)
        schedule_layout.addWidget(self.weekly_widget)
        
        # Cron 设置
        self.cron_widget = QWidget()
        cron_layout = QHBoxLayout(self.cron_widget)
        cron_layout.setContentsMargins(0, 0, 0, 0)
        cron_layout.addWidget(QLabel("Cron:"))
        self.cron_edit = QLineEdit()
        self.cron_edit.setPlaceholderText("分 时 日 月 周 (例: 0 4 * * *)")
        self.cron_edit.setText("0 4 * * *")
        cron_layout.addWidget(self.cron_edit, 1)
        self.cron_widget.setVisible(False)
        schedule_layout.addWidget(self.cron_widget)
        
        layout.addWidget(schedule_group)
        
        # 任务配置
        config_group = QGroupBox("任务配置")
        config_layout = QGridLayout(config_group)
        
        config_layout.addWidget(QLabel("运行模式:"), 0, 0)
        self.pipeline_combo = QComboBox()
        self.pipeline_combo.addItem("FULL - 在线抓取 + 入库", "FULL")
        self.pipeline_combo.addItem("INGEST_ONLY - 仅入库", "INGEST_ONLY")
        config_layout.addWidget(self.pipeline_combo, 0, 1)
        
        config_layout.addWidget(QLabel("回溯小时:"), 1, 0)
        self.lookback_hours = QSpinBox()
        self.lookback_hours.setRange(1, 720)
        self.lookback_hours.setValue(24)
        self.lookback_hours.setSuffix(" 小时")
        self.lookback_hours.setToolTip("每次执行时，抓取最近 N 小时的数据")
        config_layout.addWidget(self.lookback_hours, 1, 1)
        
        layout.addWidget(config_group)
        
        # CLI 命令行预览
        cli_group = QGroupBox("命令行预览")
        cli_layout = QVBoxLayout(cli_group)
        
        self.cli_preview = QPlainTextEdit()
        self.cli_preview.setMaximumHeight(100)
        self.cli_preview.setFont(QFont("Consolas", 9))
        self.cli_preview.setPlaceholderText("CLI 命令行将在此显示...")
        cli_layout.addWidget(self.cli_preview)
        
        # CLI 编辑提示和复制按钮
        cli_btn_layout = QHBoxLayout()
        self.cli_editable_check = QCheckBox("允许手动编辑")
        self.cli_editable_check.setToolTip("勾选后可以手动修改命令行参数")
        self.cli_editable_check.stateChanged.connect(self._on_cli_editable_changed)
        cli_btn_layout.addWidget(self.cli_editable_check)
        
        cli_btn_layout.addStretch()
        
        self.copy_cli_btn = QPushButton("复制命令")
        self.copy_cli_btn.setProperty("secondary", True)
        self.copy_cli_btn.clicked.connect(self._copy_cli_to_clipboard)
        cli_btn_layout.addWidget(self.copy_cli_btn)
        
        self.refresh_cli_btn = QPushButton("刷新预览")
        self.refresh_cli_btn.setProperty("secondary", True)
        self.refresh_cli_btn.clicked.connect(self._update_cli_preview)
        cli_btn_layout.addWidget(self.refresh_cli_btn)
        
        cli_layout.addLayout(cli_btn_layout)
        
        layout.addWidget(cli_group)
        
        # 按钮
        btn_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        btn_box.accepted.connect(self.accept)
        btn_box.rejected.connect(self.reject)
        layout.addWidget(btn_box)
    
    def _on_type_changed(self, index: int):
        schedule_type = self.schedule_type_combo.currentData()
        self.interval_widget.setVisible(schedule_type == ScheduleType.INTERVAL)
        self.daily_widget.setVisible(schedule_type == ScheduleType.DAILY)
        self.weekly_widget.setVisible(schedule_type == ScheduleType.WEEKLY)
        self.cron_widget.setVisible(schedule_type == ScheduleType.CRON)
    
    def _load_task(self, task: ScheduledTask):
        """加载任务数据"""
        self.name_edit.setText(task.name)
        self.enabled_check.setChecked(task.enabled)
        
        # 选择任务
        for i in range(self.task_list.count()):
            item = self.task_list.item(i)
            code = item.data(Qt.UserRole)
            item.setSelected(code in task.task_codes)
        
        # 调度设置
        schedule = task.schedule
        
        # 设置类型
        for i in range(self.schedule_type_combo.count()):
            if self.schedule_type_combo.itemData(i) == schedule.schedule_type:
                self.schedule_type_combo.setCurrentIndex(i)
                break
        
        self.interval_value.setValue(schedule.interval_value)
        for i in range(self.interval_unit.count()):
            if self.interval_unit.itemData(i) == schedule.interval_unit:
                self.interval_unit.setCurrentIndex(i)
                break
        
        if schedule.daily_time:
            h, m = map(int, schedule.daily_time.split(":"))
            self.daily_time.setTime(QTime(h, m))
        
        for day, check in self.day_checks.items():
            check.setChecked(day in schedule.weekly_days)
        
        if schedule.weekly_time:
            h, m = map(int, schedule.weekly_time.split(":"))
            self.weekly_time.setTime(QTime(h, m))
        
        self.cron_edit.setText(schedule.cron_expression)
        
        # 任务配置
        if task.task_config.get("pipeline_flow"):
            for i in range(self.pipeline_combo.count()):
                if self.pipeline_combo.itemData(i) == task.task_config["pipeline_flow"]:
                    self.pipeline_combo.setCurrentIndex(i)
                    break
        
        self.lookback_hours.setValue(task.task_config.get("lookback_hours", 24))
    
    def get_task(self) -> Optional[ScheduledTask]:
        """获取配置的任务"""
        name = self.name_edit.text().strip()
        if not name:
            QMessageBox.warning(self, "提示", "请输入任务名称")
            return None
        
        # 获取选中的任务
        task_codes = []
        for i in range(self.task_list.count()):
            item = self.task_list.item(i)
            if item.isSelected():
                task_codes.append(item.data(Qt.UserRole))
        
        if not task_codes:
            QMessageBox.warning(self, "提示", "请至少选择一个任务")
            return None
        
        # 构建调度配置
        schedule_type = self.schedule_type_combo.currentData()
        
        weekly_days = [day for day, check in self.day_checks.items() if check.isChecked()]
        
        schedule = ScheduleConfig(
            schedule_type=schedule_type,
            interval_value=self.interval_value.value(),
            interval_unit=self.interval_unit.currentData(),
            daily_time=self.daily_time.time().toString("HH:mm"),
            weekly_days=weekly_days or [1],
            weekly_time=self.weekly_time.time().toString("HH:mm"),
            cron_expression=self.cron_edit.text().strip(),
            enabled=True,
        )
        
        # 构建任务配置
        task_config = {
            "pipeline_flow": self.pipeline_combo.currentData(),
            "lookback_hours": self.lookback_hours.value(),
        }
        
        # 创建或更新任务
        if self.task:
            task = self.task
            task.name = name
            task.task_codes = task_codes
            task.schedule = schedule
            task.task_config = task_config
            task.enabled = self.enabled_check.isChecked()
        else:
            task = ScheduledTask(
                id=str(uuid.uuid4())[:8],
                name=name,
                task_codes=task_codes,
                schedule=schedule,
                task_config=task_config,
                enabled=self.enabled_check.isChecked(),
            )
        
        task.update_next_run()
        return task
    
    def _connect_preview_signals(self):
        """连接信号以实时更新 CLI 预览"""
        # 任务选择变化
        self.task_list.itemSelectionChanged.connect(self._update_cli_preview)
        
        # 调度配置变化
        self.schedule_type_combo.currentIndexChanged.connect(self._update_cli_preview)
        self.interval_value.valueChanged.connect(self._update_cli_preview)
        self.interval_unit.currentIndexChanged.connect(self._update_cli_preview)
        
        # 任务配置变化
        self.pipeline_combo.currentIndexChanged.connect(self._update_cli_preview)
        self.lookback_hours.valueChanged.connect(self._update_cli_preview)
    
    def _update_cli_preview(self):
        """更新 CLI 命令行预览"""
        # 如果用户正在手动编辑，不自动更新
        if self.cli_editable_check.isChecked():
            return
        
        # 获取选中的任务
        task_codes = []
        for i in range(self.task_list.count()):
            item = self.task_list.item(i)
            if item.isSelected():
                task_codes.append(item.data(Qt.UserRole))
        
        if not task_codes:
            self.cli_preview.setPlainText("# 请选择至少一个任务")
            return
        
        # 获取配置
        lookback_hours = self.lookback_hours.value()
        pipeline_flow = self.pipeline_combo.currentData()
        
        # 构建说明注释
        lines = []
        
        # 调度规则说明
        schedule_type = self.schedule_type_combo.currentData()
        if schedule_type == ScheduleType.INTERVAL:
            interval_val = self.interval_value.value()
            interval_unit = self.interval_unit.currentText()
            lines.append(f"# 调度：每 {interval_val} {interval_unit} 执行一次")
        elif schedule_type == ScheduleType.DAILY:
            daily_time = self.daily_time.time().toString("HH:mm")
            lines.append(f"# 调度：每天 {daily_time} 执行")
        elif schedule_type == ScheduleType.WEEKLY:
            weekly_time = self.weekly_time.time().toString("HH:mm")
            days = [f"周{['一','二','三','四','五','六','日'][d-1]}" 
                    for d, c in self.day_checks.items() if c.isChecked()]
            lines.append(f"# 调度：每周 {','.join(days)} {weekly_time} 执行")
        elif schedule_type == ScheduleType.CRON:
            cron_expr = self.cron_edit.text().strip()
            lines.append(f"# 调度：Cron 表达式 {cron_expr}")
        
        # 动态时间窗口说明
        lines.append(f"# 回溯窗口：{lookback_hours} 小时")
        lines.append("#")
        lines.append("# ⚠ 时间窗口在每次执行时动态计算：")
        lines.append(f"#   --window-start = <执行时间> - {lookback_hours}h")
        lines.append(f"#   --window-end   = <执行时间>")
        lines.append("")
        
        # 生成命令行（使用占位符表示动态时间）
        tasks_str = ",".join(task_codes)
        cmd_parts = [
            "python -m cli.main",
            f"--tasks {tasks_str}",
            f"--pipeline-flow {pipeline_flow}",
            f'--window-start "<执行时间 - {lookback_hours}h>"',
            f'--window-end "<执行时间>"',
        ]
        lines.append(" \\\n    ".join(cmd_parts))
        
        # 添加示例（使用当前时间作为示例）
        lines.append("")
        lines.append("# -------- 示例（假设现在执行）--------")
        now = datetime.now()
        start_time = now - timedelta(hours=lookback_hours)
        
        # 构建示例 TaskConfig
        config = TaskConfig(
            tasks=task_codes,
            pipeline_flow=pipeline_flow,
            window_start=start_time.strftime("%Y-%m-%d %H:%M:%S"),
            window_end=now.strftime("%Y-%m-%d %H:%M:%S"),
        )
        example_cmd = self.cli_builder.build_command_string(config)
        lines.append(f"# {example_cmd}")
        
        self.cli_preview.setPlainText("\n".join(lines))
    
    def _on_cli_editable_changed(self, state):
        """切换 CLI 编辑模式"""
        editable = state == Qt.Checked
        self.cli_preview.setReadOnly(not editable)
        
        if editable:
            # 切换到编辑模式，提示用户
            current_text = self.cli_preview.toPlainText()
            if not current_text.startswith("# [手动编辑模式]"):
                self.cli_preview.setPlainText(f"# [手动编辑模式] 修改后点击「刷新预览」可恢复自动生成\n{current_text}")
        else:
            # 切换回只读模式，刷新预览
            self._update_cli_preview()
    
    def _copy_cli_to_clipboard(self):
        """复制 CLI 命令到剪贴板"""
        from PySide6.QtWidgets import QApplication
        text = self.cli_preview.toPlainText()
        # 提取实际命令行（跳过注释行）
        lines = text.split('\n')
        cmd_lines = [line for line in lines if line.strip() and not line.strip().startswith('#')]
        cmd_text = '\n'.join(cmd_lines)
        
        if cmd_text:
            QApplication.clipboard().setText(cmd_text)
            QMessageBox.information(self, "提示", "命令行已复制到剪贴板")
        else:
            QMessageBox.warning(self, "提示", "没有可复制的命令")


class ScheduleLogDialog(QDialog):
    """调度任务日志查看对话框"""
    
    def __init__(self, scheduled_task: ScheduledTask, task_history: List[QueuedTask], 
                 task_queue: List[QueuedTask] = None, parent=None):
        super().__init__(parent)
        self.scheduled_task = scheduled_task
        self.task_history = task_history  # 引用任务管理器的执行历史
        self.task_queue = task_queue or []  # 引用任务队列（用于获取执行中任务的实时日志）
        self.setWindowTitle(f"调度日志 - {scheduled_task.name}")
        self.setMinimumSize(900, 600)
        self._init_ui()
        
        # 定时刷新执行中任务的日志
        self._refresh_timer = QTimer(self)
        self._refresh_timer.timeout.connect(self._refresh_running_log)
        self._refresh_timer.start(1000)  # 每秒刷新
    
    def _init_ui(self):
        layout = QVBoxLayout(self)
        
        # 调度任务基本信息
        info_group = QGroupBox("调度任务信息")
        info_layout = QGridLayout(info_group)
        
        info_layout.addWidget(QLabel("任务名称:"), 0, 0)
        info_layout.addWidget(QLabel(self.scheduled_task.name), 0, 1)
        
        info_layout.addWidget(QLabel("执行任务:"), 0, 2)
        tasks_str = ", ".join(self.scheduled_task.task_codes[:3])
        if len(self.scheduled_task.task_codes) > 3:
            tasks_str += f" (+{len(self.scheduled_task.task_codes) - 3})"
        info_layout.addWidget(QLabel(tasks_str), 0, 3)
        
        info_layout.addWidget(QLabel("调度规则:"), 1, 0)
        info_layout.addWidget(QLabel(self.scheduled_task.schedule.get_description()), 1, 1)
        
        info_layout.addWidget(QLabel("执行次数:"), 1, 2)
        info_layout.addWidget(QLabel(str(self.scheduled_task.run_count)), 1, 3)
        
        layout.addWidget(info_group)
        
        # 分割器
        splitter = QSplitter(Qt.Vertical)
        layout.addWidget(splitter, 1)
        
        # 执行历史列表
        history_group = QGroupBox(f"执行历史 (最近 {len(self.scheduled_task.execution_history)} 次)")
        history_layout = QVBoxLayout(history_group)
        
        self.history_table = QTableWidget()
        self.history_table.setColumnCount(5)
        self.history_table.setHorizontalHeaderLabels(["执行时间", "状态", "耗时", "退出码", "摘要"])
        self.history_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
        self.history_table.horizontalHeader().setSectionResizeMode(4, QHeaderView.Stretch)
        self.history_table.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.history_table.setSelectionMode(QAbstractItemView.SingleSelection)
        self.history_table.itemSelectionChanged.connect(self._on_selection_changed)
        history_layout.addWidget(self.history_table)
        
        splitter.addWidget(history_group)
        
        # 日志详情
        log_group = QGroupBox("执行日志")
        log_layout = QVBoxLayout(log_group)
        
        self.log_text = QPlainTextEdit()
        self.log_text.setReadOnly(True)
        self.log_text.setFont(QFont("Consolas", 9))
        self.log_text.setPlaceholderText("选择上方的执行记录查看详细日志...")
        log_layout.addWidget(self.log_text)
        
        splitter.addWidget(log_group)
        splitter.setSizes([250, 350])
        
        # 按钮
        btn_layout = QHBoxLayout()
        
        copy_btn = QPushButton("复制日志")
        copy_btn.clicked.connect(self._copy_log)
        btn_layout.addWidget(copy_btn)
        
        btn_layout.addStretch()
        
        close_btn = QPushButton("关闭")
        close_btn.clicked.connect(self.accept)
        btn_layout.addWidget(close_btn)
        
        layout.addLayout(btn_layout)
        
        # 填充历史表格
        self._load_history()
    
    def _load_history(self):
        """加载执行历史"""
        records = self.scheduled_task.execution_history
        self.history_table.setRowCount(len(records))
        
        for row, record in enumerate(records):
            # 执行时间
            time_str = record.executed_at.strftime("%Y-%m-%d %H:%M:%S")
            self.history_table.setItem(row, 0, QTableWidgetItem(time_str))
            
            # 状态
            status_item = QTableWidgetItem(self._get_status_text(record.status))
            status_item.setForeground(self._get_status_color(record.status))
            self.history_table.setItem(row, 1, status_item)
            
            # 耗时
            if record.duration_seconds > 0:
                if record.duration_seconds < 60:
                    duration_str = f"{record.duration_seconds:.1f}秒"
                else:
                    mins = int(record.duration_seconds // 60)
                    secs = int(record.duration_seconds % 60)
                    duration_str = f"{mins}分{secs}秒"
            else:
                duration_str = "-"
            self.history_table.setItem(row, 2, QTableWidgetItem(duration_str))
            
            # 退出码
            exit_str = str(record.exit_code) if record.exit_code is not None else "-"
            self.history_table.setItem(row, 3, QTableWidgetItem(exit_str))
            
            # 摘要
            summary_item = QTableWidgetItem(record.summary[:50] if record.summary else "-")
            summary_item.setData(Qt.UserRole, record.task_id)  # 存储关联的任务ID
            self.history_table.setItem(row, 4, summary_item)
        
        # 自动选择第一行
        if records:
            self.history_table.selectRow(0)
    
    def _on_selection_changed(self):
        """选择变化时显示对应的日志"""
        row = self.history_table.currentRow()
        if row < 0 or row >= len(self.scheduled_task.execution_history):
            self.log_text.clear()
            return
        
        record = self.scheduled_task.execution_history[row]
        
        # 如果是执行中的任务，从任务队列获取实时日志
        if record.status == "pending":
            self._show_running_task_log(record)
            return
        
        # 优先使用执行记录中保存的日志
        if record.output:
            log_content = record.output
            # 如果有错误信息，附加到末尾
            if record.error:
                log_content += f"\n\n===== 错误信息 =====\n{record.error}"
            self.log_text.setPlainText(log_content)
        else:
            # 尝试从任务历史中查找（兼容旧记录）
            queued_task = None
            for task in self.task_history:
                if task.id == record.task_id:
                    queued_task = task
                    break
            
            if queued_task and queued_task.output:
                self.log_text.setPlainText(queued_task.output)
            else:
                # 显示基本信息
                info_lines = [
                    f"执行时间: {record.executed_at.strftime('%Y-%m-%d %H:%M:%S')}",
                    f"状态: {self._get_status_text(record.status)}",
                    f"耗时: {record.duration_seconds:.1f} 秒",
                    f"退出码: {record.exit_code}",
                    f"",
                    f"执行摘要:",
                    record.summary if record.summary else "(无)",
                ]
                if record.error:
                    info_lines.extend(["", "错误信息:", record.error])
                self.log_text.setPlainText("\n".join(info_lines))
    
    def _show_running_task_log(self, record: ScheduleExecutionRecord):
        """显示执行中任务的实时日志"""
        # 1. 先从任务队列中查找正在执行的任务
        found_task = None
        task_source = "queue"
        for task in self.task_queue:
            if task.id == record.task_id:
                found_task = task
                break
        
        # 2. 如果队列中找不到，可能任务刚完成还在历史中，从历史中查找
        if not found_task:
            for task in self.task_history:
                if task.id == record.task_id:
                    found_task = task
                    task_source = "history"
                    break
        
        if found_task and found_task.output:
            # 显示日志
            if task_source == "queue" and found_task.status == TaskStatus.RUNNING:
                # 任务还在执行中
                header = f"===== 任务执行中 (实时日志) =====\n"
                header += f"任务 ID: {found_task.id}\n"
                if found_task.started_at:
                    elapsed = (datetime.now() - found_task.started_at).total_seconds()
                    header += f"已运行: {elapsed:.0f} 秒\n"
                header += "=" * 40 + "\n\n"
            else:
                # 任务已完成（可能是刚完成，记录状态还没更新）
                status_text = "成功" if found_task.status == TaskStatus.SUCCESS else "已完成"
                if found_task.status == TaskStatus.FAILED:
                    status_text = "失败"
                header = f"===== 任务 {status_text} =====\n"
                header += f"任务 ID: {found_task.id}\n"
                if found_task.started_at and found_task.finished_at:
                    duration = (found_task.finished_at - found_task.started_at).total_seconds()
                    header += f"执行耗时: {duration:.1f} 秒\n"
                header += "=" * 40 + "\n\n"
            self.log_text.setPlainText(header + found_task.output)
        else:
            # 任务可能还未开始
            info_lines = [
                "===== 任务执行中 =====",
                f"任务 ID: {record.task_id}",
                f"开始时间: {record.executed_at.strftime('%Y-%m-%d %H:%M:%S')}",
                "",
                "日志正在生成中，请稍候...",
                "(日志将在任务执行过程中实时更新)",
            ]
            self.log_text.setPlainText("\n".join(info_lines))
    
    def _refresh_running_log(self):
        """定时刷新执行中任务的日志"""
        row = self.history_table.currentRow()
        if row < 0 or row >= len(self.scheduled_task.execution_history):
            return
        
        record = self.scheduled_task.execution_history[row]
        
        # 检查任务是否还在执行中
        if record.status == "pending":
            # 保存当前滚动位置
            scrollbar = self.log_text.verticalScrollBar()
            at_bottom = scrollbar.value() >= scrollbar.maximum() - 10
            
            # 检查任务是否已经完成（可能记录状态还没更新）
            task_completed = False
            for task in self.task_history:
                if task.id == record.task_id:
                    task_completed = True
                    break
            
            if task_completed:
                # 任务已完成，刷新历史表格以更新状态显示
                # 重新加载历史数据（从 schedule_store 获取最新状态）
                # 注意：这里需要从父窗口重新获取调度任务的最新状态
                self._show_running_task_log(record)
                # 触发重新选择以更新显示
                self._on_selection_changed()
            else:
                self._show_running_task_log(record)
            
            # 如果之前在底部，保持在底部
            if at_bottom:
                scrollbar.setValue(scrollbar.maximum())
    
    def _copy_log(self):
        """复制日志到剪贴板"""
        from PySide6.QtWidgets import QApplication
        text = self.log_text.toPlainText()
        if text:
            QApplication.clipboard().setText(text)
            QMessageBox.information(self, "提示", "日志已复制到剪贴板")
    
    @staticmethod
    def _get_status_text(status: str) -> str:
        return {
            "pending": "执行中",
            "success": "成功",
            "failed": "失败",
        }.get(status, status or "未知")
    
    @staticmethod
    def _get_status_color(status: str) -> QColor:
        return {
            "pending": QColor("#1a73e8"),
            "success": QColor("#1e8e3e"),
            "failed": QColor("#d93025"),
        }.get(status, QColor("#333333"))


class TaskManager(QWidget):
    """任务管理器"""
    
    # 信号
    task_started = Signal(str)  # 任务开始
    task_finished = Signal(bool, str)  # 任务完成
    log_message = Signal(str)  # 日志消息
    
    def __init__(self, parent=None):
        super().__init__(parent)
        self.cli_builder = CLIBuilder()
        self.task_queue: List[QueuedTask] = []
        self.task_history: List[QueuedTask] = []
        self.current_worker: Optional[TaskWorker] = None
        self.auto_run = False
        
        # 调度任务存储
        self.schedule_store = ScheduleStore()
        self.scheduler_enabled = False
        
        # 任务ID到调度任务ID的映射，用于在任务完成时更新调度执行记录
        self._task_schedule_mapping: Dict[str, str] = {}
        
        self._init_ui()
        self._connect_signals()
        
        # 定时刷新
        self.refresh_timer = QTimer(self)
        self.refresh_timer.timeout.connect(self._refresh_display)
        self.refresh_timer.start(1000)  # 每秒刷新
        
        # 调度检查定时器
        self.schedule_timer = QTimer(self)
        self.schedule_timer.timeout.connect(self._check_scheduled_tasks)
        
        # 加载调度任务
        self._refresh_schedule_table()
        
        # 加载任务历史
        self._load_task_history()
    
    def _init_ui(self):
        """初始化界面"""
        layout = QVBoxLayout(self)
        layout.setContentsMargins(16, 16, 16, 16)
        layout.setSpacing(16)
        
        # 标题
        title = QLabel("任务管理")
        title.setProperty("heading", True)
        layout.addWidget(title)
        
        # 使用选项卡
        tab_widget = QTabWidget()
        layout.addWidget(tab_widget, 1)
        
        # ====== 任务队列选项卡 ======
        queue_tab = QWidget()
        queue_tab_layout = QVBoxLayout(queue_tab)
        queue_tab_layout.setContentsMargins(0, 8, 0, 0)
        
        # 控制按钮
        header_layout = QHBoxLayout()
        self.auto_run_btn = QPushButton("自动执行: 关")
        self.auto_run_btn.setProperty("secondary", True)
        self.auto_run_btn.setCheckable(True)
        header_layout.addWidget(self.auto_run_btn)
        
        self.clear_queue_btn = QPushButton("清空队列")
        self.clear_queue_btn.setProperty("secondary", True)
        header_layout.addWidget(self.clear_queue_btn)
        header_layout.addStretch()
        queue_tab_layout.addLayout(header_layout)
        
        # 分割器
        splitter = QSplitter(Qt.Vertical)
        queue_tab_layout.addWidget(splitter, 1)
        
        # 任务队列
        queue_group = QGroupBox("任务队列")
        queue_layout = QVBoxLayout(queue_group)
        
        self.queue_table = QTableWidget()
        self.queue_table.setColumnCount(5)
        self.queue_table.setHorizontalHeaderLabels(["ID", "任务", "状态", "创建时间", "操作"])
        self.queue_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
        self.queue_table.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.queue_table.setContextMenuPolicy(Qt.CustomContextMenu)
        queue_layout.addWidget(self.queue_table)
        
        queue_btn_layout = QHBoxLayout()
        self.run_next_btn = QPushButton("执行下一个")
        self.run_all_btn = QPushButton("执行全部")
        self.stop_btn = QPushButton("停止当前")
        self.stop_btn.setProperty("danger", True)
        self.stop_btn.setEnabled(False)
        queue_btn_layout.addWidget(self.run_next_btn)
        queue_btn_layout.addWidget(self.run_all_btn)
        queue_btn_layout.addStretch()
        queue_btn_layout.addWidget(self.stop_btn)
        queue_layout.addLayout(queue_btn_layout)
        
        splitter.addWidget(queue_group)
        
        # 实时日志
        log_group = QGroupBox("执行日志 (实时)")
        log_layout = QVBoxLayout(log_group)
        
        self.live_log = QPlainTextEdit()
        self.live_log.setReadOnly(True)
        self.live_log.setFont(QFont("Consolas", 9))
        self.live_log.setMaximumBlockCount(1000)  # 限制行数
        self.live_log.setPlaceholderText("任务执行时，日志将在此实时显示...")
        log_layout.addWidget(self.live_log)
        
        log_btn_layout = QHBoxLayout()
        self.clear_log_btn = QPushButton("清空日志")
        self.clear_log_btn.setProperty("secondary", True)
        self.clear_log_btn.clicked.connect(self.live_log.clear)
        self.auto_scroll_check = QCheckBox("自动滚动")
        self.auto_scroll_check.setChecked(True)
        log_btn_layout.addWidget(self.clear_log_btn)
        log_btn_layout.addWidget(self.auto_scroll_check)
        log_btn_layout.addStretch()
        log_layout.addLayout(log_btn_layout)
        
        splitter.addWidget(log_group)
        
        # 执行历史
        history_group = QGroupBox("执行历史")
        history_layout = QVBoxLayout(history_group)
        
        self.history_table = QTableWidget()
        self.history_table.setColumnCount(6)
        self.history_table.setHorizontalHeaderLabels(["ID", "任务", "状态", "开始时间", "耗时", "结果"])
        self.history_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
        self.history_table.horizontalHeader().setSectionResizeMode(5, QHeaderView.Stretch)
        self.history_table.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.history_table.setContextMenuPolicy(Qt.CustomContextMenu)
        history_layout.addWidget(self.history_table)
        
        # 提示标签
        hint_label = QLabel("提示：双击任务可查看详细日志")
        hint_label.setProperty("subheading", True)
        history_layout.addWidget(hint_label)
        
        history_btn_layout = QHBoxLayout()
        self.clear_history_btn = QPushButton("清空历史")
        self.clear_history_btn.setProperty("secondary", True)
        history_btn_layout.addStretch()
        history_btn_layout.addWidget(self.clear_history_btn)
        history_layout.addLayout(history_btn_layout)
        
        splitter.addWidget(history_group)
        splitter.setSizes([200, 250, 250])
        
        tab_widget.addTab(queue_tab, "任务队列")
        
        # ====== 定时调度选项卡 ======
        schedule_tab = QWidget()
        schedule_layout = QVBoxLayout(schedule_tab)
        schedule_layout.setContentsMargins(0, 8, 0, 0)
        
        # 调度控制
        schedule_header = QHBoxLayout()
        
        self.scheduler_btn = QPushButton("调度器: 关")
        self.scheduler_btn.setProperty("secondary", True)
        self.scheduler_btn.setCheckable(True)
        self.scheduler_btn.setToolTip("开启后将自动执行到期的调度任务")
        schedule_header.addWidget(self.scheduler_btn)
        
        schedule_header.addStretch()
        
        self.add_schedule_btn = QPushButton("新建调度")
        schedule_header.addWidget(self.add_schedule_btn)
        
        schedule_layout.addLayout(schedule_header)
        
        # 调度任务表格
        self.schedule_table = QTableWidget()
        self.schedule_table.setColumnCount(7)
        self.schedule_table.setHorizontalHeaderLabels([
            "名称", "任务", "调度", "下次执行", "上次执行", "执行次数", "状态"
        ])
        self.schedule_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
        self.schedule_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
        self.schedule_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeToContents)
        self.schedule_table.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.schedule_table.setContextMenuPolicy(Qt.CustomContextMenu)
        schedule_layout.addWidget(self.schedule_table, 1)
        
        # 调度说明
        schedule_note = QLabel(
            "提示: 双击调度任务可查看执行日志。"
            "调度器运行时会自动检查并执行到期的任务。"
            "新建调度任务首次执行将延迟 60 秒。"
        )
        schedule_note.setProperty("subheading", True)
        schedule_note.setWordWrap(True)
        schedule_layout.addWidget(schedule_note)
        
        tab_widget.addTab(schedule_tab, "定时调度")
    
    def _connect_signals(self):
        """连接信号"""
        self.auto_run_btn.toggled.connect(self._toggle_auto_run)
        self.clear_queue_btn.clicked.connect(self._clear_queue)
        self.run_next_btn.clicked.connect(self._run_next)
        self.run_all_btn.clicked.connect(self._run_all)
        self.stop_btn.clicked.connect(self._stop_current)
        self.clear_history_btn.clicked.connect(self._clear_history)
        self.queue_table.customContextMenuRequested.connect(self._show_queue_menu)
        
        # 历史表格
        self.history_table.customContextMenuRequested.connect(self._show_history_menu)
        self.history_table.doubleClicked.connect(self._view_task_log)
        
        # 调度相关
        self.scheduler_btn.toggled.connect(self._toggle_scheduler)
        self.add_schedule_btn.clicked.connect(self._add_schedule)
        self.schedule_table.customContextMenuRequested.connect(self._show_schedule_menu)
        self.schedule_table.doubleClicked.connect(self._on_schedule_double_click)
    
    def add_task(self, config: TaskConfig) -> str:
        """添加任务到队列"""
        task_id = str(uuid.uuid4())[:8]
        task = QueuedTask(
            id=task_id,
            config=config,
            status=TaskStatus.PENDING,
        )
        self.task_queue.append(task)
        self._refresh_queue_table()
        
        # 如果开启了自动执行且当前没有任务在运行
        if self.auto_run and not self._is_running():
            self._run_next()
        
        return task_id
    
    def _toggle_auto_run(self, checked: bool):
        """切换自动执行"""
        self.auto_run = checked
        self.auto_run_btn.setText(f"自动执行: {'开' if checked else '关'}")
        
        # 如果开启自动执行且有待执行任务
        if checked and self.task_queue and not self._is_running():
            self._run_next()
    
    def _clear_queue(self):
        """清空队列"""
        # 只清除未执行的任务
        self.task_queue = [t for t in self.task_queue if t.status == TaskStatus.RUNNING]
        self._refresh_queue_table()
    
    def _run_next(self):
        """执行下一个任务"""
        if self._is_running():
            QMessageBox.information(self, "提示", "当前有任务正在执行")
            return
        
        # 找到下一个待执行的任务
        for task in self.task_queue:
            if task.status == TaskStatus.PENDING:
                self._execute_task(task)
                break
    
    def _run_all(self):
        """执行全部任务"""
        self.auto_run = True
        self.auto_run_btn.setChecked(True)
        if not self._is_running():
            self._run_next()
    
    def _stop_current(self):
        """停止当前任务"""
        if self.current_worker and self.current_worker.isRunning():
            self.current_worker.stop()
    
    def _clear_history(self):
        """清空历史"""
        self.task_history.clear()
        self._refresh_history_table()
        self._save_task_history()  # 保存到文件
    
    def _execute_task(self, task: QueuedTask):
        """执行任务"""
        # 构建命令
        cmd = self.cli_builder.build_command(task.config)
        
        # 获取额外环境变量
        extra_env = task.config.env_vars if hasattr(task.config, 'env_vars') else {}
        
        # 创建工作线程
        self.current_worker = TaskWorker(cmd, extra_env=extra_env)
        self.current_worker.output_received.connect(lambda line: self._on_output(task, line))
        self.current_worker.task_finished.connect(lambda code, summary: self._on_finished(task, code, summary))
        self.current_worker.error_occurred.connect(lambda error: self._on_error(task, error))
        
        # 更新任务状态
        task.status = TaskStatus.RUNNING
        task.started_at = datetime.now()
        
        # 更新 UI
        self.stop_btn.setEnabled(True)
        self._refresh_queue_table()
        
        # 发送信号
        task_info = ",".join(task.config.tasks[:2])
        if len(task.config.tasks) > 2:
            task_info += f" 等{len(task.config.tasks)}个"
        self.task_started.emit(task_info)
        
        # 在实时日志中显示任务开始信息
        self.live_log.appendPlainText("")
        self.live_log.appendPlainText("=" * 60)
        self.live_log.appendPlainText(f"▶ 开始执行任务: {task_info}")
        self.live_log.appendPlainText(f"  任务 ID: {task.id}")
        self.live_log.appendPlainText(f"  开始时间: {task.started_at.strftime('%Y-%m-%d %H:%M:%S')}")
        self.live_log.appendPlainText("=" * 60)
        
        # 启动
        self.current_worker.start()
    
    def _on_output(self, task: QueuedTask, line: str):
        """收到输出"""
        task.output += line + "\n"
        self.log_message.emit(line)
        
        # 显示到实时日志区域
        timestamp = datetime.now().strftime("%H:%M:%S")
        self.live_log.appendPlainText(f"[{timestamp}] {line}")
        
        # 自动滚动
        if self.auto_scroll_check.isChecked():
            scrollbar = self.live_log.verticalScrollBar()
            scrollbar.setValue(scrollbar.maximum())
    
    def _on_finished(self, task: QueuedTask, exit_code: int, summary: str):
        """任务完成"""
        task.finished_at = datetime.now()
        task.exit_code = exit_code
        
        if exit_code == 0:
            task.status = TaskStatus.SUCCESS
        else:
            task.status = TaskStatus.FAILED
            task.error = summary
        
        # 在实时日志中显示任务完成信息（详细报告）
        duration = (task.finished_at - task.started_at).total_seconds() if task.started_at else 0
        status_text = "✓ 成功" if exit_code == 0 else f"✗ 失败 (退出码: {exit_code})"
        
        self.live_log.appendPlainText("")
        self.live_log.appendPlainText("=" * 60)
        self.live_log.appendPlainText(f"■ 任务执行报告")
        self.live_log.appendPlainText("=" * 60)
        self.live_log.appendPlainText(f"  状态: {status_text}")
        self.live_log.appendPlainText(f"  任务 ID: {task.id}")
        self.live_log.appendPlainText(f"  任务列表: {', '.join(task.config.tasks)}")
        self.live_log.appendPlainText(f"  开始时间: {task.started_at.strftime('%Y-%m-%d %H:%M:%S') if task.started_at else '-'}")
        self.live_log.appendPlainText(f"  结束时间: {task.finished_at.strftime('%Y-%m-%d %H:%M:%S') if task.finished_at else '-'}")
        
        # 格式化耗时
        if duration < 60:
            duration_str = f"{duration:.1f} 秒"
        elif duration < 3600:
            mins = int(duration // 60)
            secs = int(duration % 60)
            duration_str = f"{mins} 分 {secs} 秒"
        else:
            hours = int(duration // 3600)
            mins = int((duration % 3600) // 60)
            duration_str = f"{hours} 时 {mins} 分"
        self.live_log.appendPlainText(f"  总耗时: {duration_str}")
        
        # 显示详细摘要
        if summary:
            self.live_log.appendPlainText("")
            self.live_log.appendPlainText("  -------- 执行摘要 --------")
            for line in summary.split('\n'):
                self.live_log.appendPlainText(f"  {line}")
        
        self.live_log.appendPlainText("=" * 60)
        self.live_log.appendPlainText("")
        
        # 【重要】先更新调度执行记录（如果此任务来自调度），确保日志能正确保存
        # 必须在移除任务之前执行，否则 ScheduleLogDialog 无法获取实时日志
        self._update_schedule_execution_record(task, duration, summary)
        
        # 移动到历史
        self.task_queue.remove(task)
        self.task_history.insert(0, task)
        
        # 限制历史记录数量
        if len(self.task_history) > 100:
            self.task_history = self.task_history[:100]
        
        # 保存历史到文件
        self._save_task_history()
        
        # 更新 UI
        self.stop_btn.setEnabled(False)
        self._refresh_queue_table()
        self._refresh_history_table()
        self._refresh_schedule_table()
        
        # 发送信号
        self.task_finished.emit(exit_code == 0, summary)
        
        # 如果开启自动执行，或者队列中有定时任务待执行，继续下一个
        if self.auto_run or self._has_scheduled_tasks_pending():
            QTimer.singleShot(500, self._run_next)
    
    def _on_error(self, task: QueuedTask, error: str):
        """发生错误"""
        task.error = error
        self.log_message.emit(f"[错误] {error}")
    
    def _is_running(self) -> bool:
        """是否有任务在运行"""
        return self.current_worker is not None and self.current_worker.isRunning()
    
    def _has_scheduled_tasks_pending(self) -> bool:
        """检查队列中是否有待执行的定时任务"""
        for task in self.task_queue:
            if task.status == TaskStatus.PENDING and task.id in self._task_schedule_mapping:
                return True
        return False
    
    def _refresh_display(self):
        """刷新显示"""
        # 更新运行中任务的状态
        if self._is_running():
            for row in range(self.queue_table.rowCount()):
                status_item = self.queue_table.item(row, 2)
                if status_item and status_item.text() == "执行中":
                    # 添加动画效果
                    dots = "." * (int(datetime.now().timestamp()) % 4)
                    status_item.setText(f"执行中{dots}")
    
    def _refresh_queue_table(self):
        """刷新队列表格"""
        self.queue_table.setRowCount(len(self.task_queue))
        
        for row, task in enumerate(self.task_queue):
            # ID
            self.queue_table.setItem(row, 0, QTableWidgetItem(task.id))
            
            # 任务
            tasks_str = ", ".join(task.config.tasks[:3])
            if len(task.config.tasks) > 3:
                tasks_str += f" (+{len(task.config.tasks) - 3})"
            self.queue_table.setItem(row, 1, QTableWidgetItem(tasks_str))
            
            # 状态
            status_item = QTableWidgetItem(self._get_status_text(task.status))
            status_item.setForeground(self._get_status_color(task.status))
            self.queue_table.setItem(row, 2, status_item)
            
            # 创建时间
            time_str = task.created_at.strftime("%H:%M:%S")
            self.queue_table.setItem(row, 3, QTableWidgetItem(time_str))
            
            # 操作按钮
            self.queue_table.setItem(row, 4, QTableWidgetItem(""))
    
    def _refresh_history_table(self):
        """刷新历史表格"""
        self.history_table.setRowCount(len(self.task_history))
        
        for row, task in enumerate(self.task_history):
            # ID
            self.history_table.setItem(row, 0, QTableWidgetItem(task.id))
            
            # 任务
            tasks_str = ", ".join(task.config.tasks[:3])
            if len(task.config.tasks) > 3:
                tasks_str += f" (+{len(task.config.tasks) - 3})"
            self.history_table.setItem(row, 1, QTableWidgetItem(tasks_str))
            
            # 状态
            status_item = QTableWidgetItem(self._get_status_text(task.status))
            status_item.setForeground(self._get_status_color(task.status))
            self.history_table.setItem(row, 2, status_item)
            
            # 开始时间
            time_str = task.started_at.strftime("%Y-%m-%d %H:%M:%S") if task.started_at else "-"
            self.history_table.setItem(row, 3, QTableWidgetItem(time_str))
            
            # 耗时
            if task.started_at and task.finished_at:
                duration = (task.finished_at - task.started_at).total_seconds()
                if duration < 60:
                    duration_str = f"{duration:.1f}秒"
                else:
                    duration_str = f"{int(duration // 60)}分{int(duration % 60)}秒"
            else:
                duration_str = "-"
            self.history_table.setItem(row, 4, QTableWidgetItem(duration_str))
            
            # 结果 - 提取摘要的第一行作为显示
            if task.status == TaskStatus.SUCCESS:
                # 从 output 中提取摘要信息
                result = self._extract_result_summary(task)
            else:
                result = task.error if task.error else "失败"
            
            # 显示结果（取第一行，最多80字符）
            result_display = result.split('\n')[0][:80] if result else "成功"
            result_item = QTableWidgetItem(result_display)
            result_item.setToolTip(result)  # 完整内容作为 tooltip
            self.history_table.setItem(row, 5, result_item)
    
    def _show_queue_menu(self, pos):
        """显示队列右键菜单"""
        item = self.queue_table.itemAt(pos)
        if not item:
            return
        
        row = item.row()
        if row >= len(self.task_queue):
            return
        
        task = self.task_queue[row]
        
        menu = QMenu(self)
        
        if task.status == TaskStatus.PENDING:
            run_action = QAction("立即执行", self)
            run_action.triggered.connect(lambda: self._execute_task(task))
            menu.addAction(run_action)
            
            remove_action = QAction("移除", self)
            remove_action.triggered.connect(lambda: self._remove_task(task))
            menu.addAction(remove_action)
        elif task.status == TaskStatus.RUNNING:
            stop_action = QAction("停止", self)
            stop_action.triggered.connect(self._stop_current)
            menu.addAction(stop_action)
        
        menu.exec(self.queue_table.mapToGlobal(pos))
    
    def _remove_task(self, task: QueuedTask):
        """移除任务"""
        if task in self.task_queue:
            self.task_queue.remove(task)
            self._refresh_queue_table()
    
    def _show_history_menu(self, pos):
        """显示历史右键菜单"""
        item = self.history_table.itemAt(pos)
        if not item:
            return
        
        row = item.row()
        if row >= len(self.task_history):
            return
        
        task = self.task_history[row]
        
        menu = QMenu(self)
        
        view_action = QAction("查看日志", self)
        view_action.triggered.connect(lambda: self._show_task_log_dialog(task))
        menu.addAction(view_action)
        
        rerun_action = QAction("重新执行", self)
        rerun_action.triggered.connect(lambda: self._rerun_task(task))
        menu.addAction(rerun_action)
        
        menu.addSeparator()
        
        remove_action = QAction("从历史删除", self)
        remove_action.triggered.connect(lambda: self._remove_from_history(task))
        menu.addAction(remove_action)
        
        menu.exec(self.history_table.mapToGlobal(pos))
    
    def _view_task_log(self, index):
        """双击查看任务日志"""
        row = index.row()
        if row < len(self.task_history):
            self._show_task_log_dialog(self.task_history[row])
    
    def _show_task_log_dialog(self, task: QueuedTask):
        """显示任务日志对话框"""
        dialog = TaskLogDialog(task, self)
        dialog.exec()
    
    def _rerun_task(self, task: QueuedTask):
        """重新执行任务"""
        # 创建新任务（使用相同配置）
        self.add_task(task.config)
        QMessageBox.information(self, "提示", "任务已添加到队列")
    
    def _remove_from_history(self, task: QueuedTask):
        """从历史中删除任务"""
        if task in self.task_history:
            self.task_history.remove(task)
            self._refresh_history_table()
            self._save_task_history()  # 保存到文件
    
    def _load_task_history(self):
        """从文件加载任务历史"""
        try:
            history_data = app_settings.load_task_history()
            for item in history_data:
                try:
                    # 重建 TaskConfig
                    config = TaskConfig(
                        tasks=item.get("tasks", []),
                        pipeline_flow=item.get("pipeline_flow", "FULL"),
                        window_start=item.get("window_start"),
                        window_end=item.get("window_end"),
                    )
                    
                    # 重建 QueuedTask
                    status_str = item.get("status", "success")
                    status_map = {
                        "pending": TaskStatus.PENDING,
                        "running": TaskStatus.RUNNING,
                        "success": TaskStatus.SUCCESS,
                        "failed": TaskStatus.FAILED,
                        "cancelled": TaskStatus.CANCELLED,
                    }
                    status = status_map.get(status_str, TaskStatus.SUCCESS)
                    
                    task = QueuedTask(
                        id=item.get("id", "unknown"),
                        config=config,
                        status=status,
                    )
                    
                    # 恢复时间
                    if item.get("created_at"):
                        task.created_at = datetime.fromisoformat(item["created_at"])
                    if item.get("started_at"):
                        task.started_at = datetime.fromisoformat(item["started_at"])
                    if item.get("finished_at"):
                        task.finished_at = datetime.fromisoformat(item["finished_at"])
                    
                    task.exit_code = item.get("exit_code")
                    task.error = item.get("error", "")
                    task.output = item.get("output_preview", "")
                    
                    self.task_history.append(task)
                except Exception:
                    continue
            
            # 刷新显示
            self._refresh_history_table()
            
            if self.task_history:
                self.log_message.emit(f"[系统] 已加载 {len(self.task_history)} 条历史任务记录")
        except Exception as e:
            print(f"加载任务历史失败: {e}")
    
    def _save_task_history(self):
        """保存任务历史到文件"""
        try:
            app_settings.save_task_history(self.task_history)
        except Exception as e:
            print(f"保存任务历史失败: {e}")
    
    def _extract_result_summary(self, task: QueuedTask) -> str:
        """从任务输出中提取结果摘要"""
        import re
        
        if not task.output:
            return "成功"
        
        lines = task.output.split('\n')
        summary_parts = []
        
        # 统计关键数据
        total_inserted = 0
        total_updated = 0
        total_missing = 0
        total_records = 0
        
        for line in lines:
            # 解析 DWD 装载统计
            if "完成，统计=" in line:
                try:
                    match = re.search(r"统计=(\{.+\})", line)
                    if match:
                        import json
                        stats_str = match.group(1).replace("'", '"')
                        stats = json.loads(stats_str)
                        

                        if 'tables' in stats:

                            for tbl in stats['tables']:

                                inserted = int(tbl.get('inserted', 0) or 0)

                                updated = int(tbl.get('updated', 0) or 0)

                                processed = int(tbl.get('processed', 0) or 0)

                                has_new_counts = ('inserted' in tbl) or ('updated' in tbl)

                                if has_new_counts:

                                    total_inserted += inserted

                                    total_updated += updated

                                else:

                                    total_inserted += inserted + processed

                except Exception:
                    pass
            
            # 解析数据校验结果
            if "结果统计:" in line or "结果统计：" in line:
                try:
                    match = re.search(r"\{.+\}", line)
                    if match:
                        import json
                        stats_str = match.group(0).replace("'", '"')
                        stats = json.loads(stats_str)
                        total_missing = stats.get('missing', 0)
                except Exception:
                    pass
            
            # 解析 CHECK_DONE
            match = re.search(r'CHECK_DONE.*records=(\d+)', line)
            if match:
                total_records += int(match.group(1))
        
        # 构建摘要
        if total_inserted > 0 or total_updated > 0:
            if total_updated > 0:
                summary_parts.append(f"?? {total_inserted} ?, ?? {total_updated} ?")
            else:
                summary_parts.append(f"?? {total_inserted} ?")
        
        if total_records > 0:
            if total_missing > 0:
                summary_parts.append(f"校验 {total_records} 条, 缺失 {total_missing}")
            else:
                summary_parts.append(f"校验 {total_records} 条, 数据完整")
        
        if summary_parts:
            return " | ".join(summary_parts)
        
        return "成功"
    
    @staticmethod
    def _get_status_text(status: TaskStatus) -> str:
        """获取状态文本"""
        return {
            TaskStatus.PENDING: "待执行",
            TaskStatus.RUNNING: "执行中",
            TaskStatus.SUCCESS: "成功",
            TaskStatus.FAILED: "失败",
            TaskStatus.CANCELLED: "已取消",
        }.get(status, "未知")
    
    @staticmethod
    def _get_status_color(status: TaskStatus) -> QColor:
        """获取状态颜色"""
        return {
            TaskStatus.PENDING: QColor("#5f6368"),
            TaskStatus.RUNNING: QColor("#1a73e8"),
            TaskStatus.SUCCESS: QColor("#1e8e3e"),
            TaskStatus.FAILED: QColor("#d93025"),
            TaskStatus.CANCELLED: QColor("#9aa0a6"),
        }.get(status, QColor("#333333"))
    
    # ========== 调度相关方法 ==========
    
    def _toggle_scheduler(self, checked: bool):
        """切换调度器状态"""
        self.scheduler_enabled = checked
        self.scheduler_btn.setText(f"调度器: {'开' if checked else '关'}")
        
        if checked:
            # 启动调度检查定时器（每分钟检查一次）
            self.schedule_timer.start(60000)
            # 立即检查一次
            self._check_scheduled_tasks()
            self.log_message.emit("[调度器] 已启动")
        else:
            self.schedule_timer.stop()
            self.log_message.emit("[调度器] 已停止")
    
    def _check_scheduled_tasks(self):
        """检查并执行到期的调度任务"""
        if not self.scheduler_enabled:
            return
        
        due_tasks = self.schedule_store.get_due_tasks()
        for task in due_tasks:
            self._execute_scheduled_task(task)
    
    def _execute_scheduled_task(self, scheduled_task: ScheduledTask):
        """执行调度任务"""
        # 构建任务配置
        lookback_hours = scheduled_task.task_config.get("lookback_hours", 24)
        now = datetime.now()
        start_time = now - timedelta(hours=lookback_hours)
        
        config = TaskConfig(
            tasks=scheduled_task.task_codes,
            pipeline_flow=scheduled_task.task_config.get("pipeline_flow", "FULL"),
            window_start=start_time.strftime("%Y-%m-%d %H:%M:%S"),
            window_end=now.strftime("%Y-%m-%d %H:%M:%S"),
        )
        
        # 添加到队列
        task_id = self.add_task(config)
        
        # 创建执行记录
        execution_record = ScheduleExecutionRecord(
            task_id=task_id,
            executed_at=now,
            status="pending",
        )
        scheduled_task.add_execution_record(execution_record)
        
        # 保存映射关系，以便任务完成时更新记录
        self._task_schedule_mapping[task_id] = scheduled_task.id
        
        # 更新调度任务状态
        scheduled_task.last_run = now
        scheduled_task.run_count += 1
        scheduled_task.last_status = "执行中"
        scheduled_task.update_next_run()
        self.schedule_store.update_task(scheduled_task)
        
        self._refresh_schedule_table()
        self.log_message.emit(f"[调度器] 执行任务: {scheduled_task.name} (ID: {task_id})")
        
        # 定时任务必须立即启动执行，不受 auto_run 设置影响
        if not self._is_running():
            # 从队列中找到刚添加的任务并执行
            for queued_task in self.task_queue:
                if queued_task.id == task_id:
                    self._execute_task(queued_task)
                    break
    
    def _add_schedule(self):
        """添加调度任务"""
        dialog = ScheduleEditDialog(parent=self)
        if dialog.exec() == QDialog.Accepted:
            task = dialog.get_task()
            if task:
                self.schedule_store.add_task(task)
                self._refresh_schedule_table()
                self.log_message.emit(f"[调度器] 已创建任务: {task.name}")
    
    def _edit_schedule(self):
        """编辑调度任务"""
        row = self.schedule_table.currentRow()
        if row < 0:
            return
        
        tasks = self.schedule_store.get_all_tasks()
        if row >= len(tasks):
            return
        
        task = tasks[row]
        dialog = ScheduleEditDialog(task=task, parent=self)
        if dialog.exec() == QDialog.Accepted:
            updated_task = dialog.get_task()
            if updated_task:
                self.schedule_store.update_task(updated_task)
                self._refresh_schedule_table()
                self.log_message.emit(f"[调度器] 已更新任务: {updated_task.name}")
    
    def _delete_schedule(self, task_id: str):
        """删除调度任务"""
        task = self.schedule_store.get_task(task_id)
        if not task:
            return
        
        reply = QMessageBox.question(
            self,
            "确认删除",
            f"确定要删除调度任务 '{task.name}' 吗？",
            QMessageBox.Yes | QMessageBox.No,
            QMessageBox.No
        )
        
        if reply == QMessageBox.Yes:
            self.schedule_store.remove_task(task_id)
            self._refresh_schedule_table()
            self.log_message.emit(f"[调度器] 已删除任务: {task.name}")
    
    def _toggle_schedule_enabled(self, task_id: str):
        """切换调度任务启用状态"""
        task = self.schedule_store.get_task(task_id)
        if task:
            task.enabled = not task.enabled
            task.update_next_run()
            self.schedule_store.update_task(task)
            self._refresh_schedule_table()
            status = "启用" if task.enabled else "禁用"
            self.log_message.emit(f"[调度器] {task.name}: {status}")
    
    def _run_schedule_now(self, task_id: str):
        """立即执行调度任务"""
        task = self.schedule_store.get_task(task_id)
        if task:
            self._execute_scheduled_task(task)
    
    def _show_schedule_menu(self, pos):
        """显示调度任务右键菜单"""
        item = self.schedule_table.itemAt(pos)
        if not item:
            return
        
        row = item.row()
        tasks = self.schedule_store.get_all_tasks()
        if row >= len(tasks):
            return
        
        task = tasks[row]
        
        menu = QMenu(self)
        
        # 查看日志
        log_action = QAction("查看执行日志", self)
        log_action.triggered.connect(lambda: self._view_schedule_log(task.id))
        menu.addAction(log_action)
        
        menu.addSeparator()
        
        # 立即执行
        run_action = QAction("立即执行", self)
        run_action.triggered.connect(lambda: self._run_schedule_now(task.id))
        menu.addAction(run_action)
        
        # 编辑
        edit_action = QAction("编辑", self)
        edit_action.triggered.connect(self._edit_schedule)
        menu.addAction(edit_action)
        
        menu.addSeparator()
        
        # 启用/禁用
        toggle_text = "禁用" if task.enabled else "启用"
        toggle_action = QAction(toggle_text, self)
        toggle_action.triggered.connect(lambda: self._toggle_schedule_enabled(task.id))
        menu.addAction(toggle_action)
        
        menu.addSeparator()
        
        # 删除
        delete_action = QAction("删除", self)
        delete_action.triggered.connect(lambda: self._delete_schedule(task.id))
        menu.addAction(delete_action)
        
        menu.exec(self.schedule_table.mapToGlobal(pos))
    
    def _refresh_schedule_table(self):
        """刷新调度任务表格"""
        tasks = self.schedule_store.get_all_tasks()
        self.schedule_table.setRowCount(len(tasks))
        
        for row, task in enumerate(tasks):
            # 名称
            name_item = QTableWidgetItem(task.name)
            name_item.setData(Qt.UserRole, task.id)
            self.schedule_table.setItem(row, 0, name_item)
            
            # 任务
            tasks_str = ", ".join(task.task_codes[:2])
            if len(task.task_codes) > 2:
                tasks_str += f" (+{len(task.task_codes) - 2})"
            self.schedule_table.setItem(row, 1, QTableWidgetItem(tasks_str))
            
            # 调度
            self.schedule_table.setItem(row, 2, QTableWidgetItem(task.schedule.get_description()))
            
            # 下次执行
            if task.next_run:
                next_str = task.next_run.strftime("%m-%d %H:%M")
            else:
                next_str = "-"
            self.schedule_table.setItem(row, 3, QTableWidgetItem(next_str))
            
            # 上次执行
            if task.last_run:
                last_str = task.last_run.strftime("%m-%d %H:%M")
            else:
                last_str = "-"
            self.schedule_table.setItem(row, 4, QTableWidgetItem(last_str))
            
            # 执行次数
            self.schedule_table.setItem(row, 5, QTableWidgetItem(str(task.run_count)))
            
            # 状态
            if task.enabled:
                status_text = "启用"
                status_color = QColor("#1e8e3e")
            else:
                status_text = "禁用"
                status_color = QColor("#9aa0a6")
            status_item = QTableWidgetItem(status_text)
            status_item.setForeground(status_color)
            self.schedule_table.setItem(row, 6, status_item)
    
    def _update_schedule_execution_record(self, task: QueuedTask, duration: float, summary: str):
        """更新调度执行记录（如果此任务来自调度）"""
        schedule_id = self._task_schedule_mapping.get(task.id)
        if not schedule_id:
            return
        
        # 从映射中移除（一次性）
        del self._task_schedule_mapping[task.id]
        
        # 获取调度任务
        scheduled_task = self.schedule_store.get_task(schedule_id)
        if not scheduled_task:
            return
        
        # 更新执行记录（包含完整日志）
        status = "success" if task.status == TaskStatus.SUCCESS else "failed"
        scheduled_task.update_execution_record(
            task_id=task.id,
            status=status,
            exit_code=task.exit_code or 0,
            duration=duration,
            summary=summary or self._extract_result_summary(task),
            output=task.output or "",
            error=task.error or "",
        )
        
        # 更新调度任务状态
        scheduled_task.last_status = "成功" if status == "success" else "失败"
        
        # 保存
        self.schedule_store.update_task(scheduled_task)
        self.log_message.emit(f"[调度器] 任务 {scheduled_task.name} 执行完成: {scheduled_task.last_status}")
    
    def _view_schedule_log(self, task_id: str):
        """查看调度任务日志"""
        task = self.schedule_store.get_task(task_id)
        if not task:
            QMessageBox.warning(self, "提示", "未找到调度任务")
            return
        
        if not task.execution_history:
            QMessageBox.information(self, "提示", "该调度任务尚无执行记录")
            return
        
        # 传递 task_queue 以便获取执行中任务的实时日志
        dialog = ScheduleLogDialog(task, self.task_history, self.task_queue, self)
        dialog.exec()
    
    def _on_schedule_double_click(self, index):
        """双击调度任务查看日志"""
        row = index.row()
        tasks = self.schedule_store.get_all_tasks()
        if row < len(tasks):
            self._view_schedule_log(tasks[row].id)
