# -*- coding: utf-8 -*-
"""任务配置面板"""

from datetime import datetime, timedelta

from PySide6.QtWidgets import (
    QWidget, QVBoxLayout, QHBoxLayout, QGridLayout,
    QGroupBox, QLabel, QLineEdit, QComboBox, QCheckBox,
    QPushButton, QPlainTextEdit, QListWidget, QListWidgetItem,
    QSplitter, QFrame, QFileDialog, QMessageBox, QScrollArea,
    QSpinBox, QTabWidget, QDateTimeEdit
)
from PySide6.QtCore import Qt, Signal, QDateTime
from PySide6.QtGui import QFont

from ..models.task_model import TaskItem, TaskConfig, TaskCategory, TASK_CATEGORIES, get_task_category
from ..utils.cli_builder import CLIBuilder
from ..utils.app_settings import app_settings
from ..workers.task_worker import TaskWorker


# ODS 自动更新任务（从 API 抓取）
AUTO_UPDATE_TASKS = [
    "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",
]

# 数据校验相关任务
INTEGRITY_CHECK_TASKS = [
    "DATA_INTEGRITY_CHECK",
]

# 所有可用任务
ALL_TASKS = [
    # ODS 任务
    ("ODS_PAYMENT", "支付流水", "抓取支付记录到 ODS"),
    ("ODS_MEMBER", "会员档案", "抓取会员信息到 ODS"),
    ("ODS_MEMBER_CARD", "会员储值卡", "抓取会员卡信息到 ODS"),
    ("ODS_MEMBER_BALANCE", "会员余额变动", "抓取会员余额变动到 ODS"),
    ("ODS_SETTLEMENT_RECORDS", "结账记录", "抓取结账记录到 ODS"),
    ("ODS_TABLE_USE", "台费计费流水", "抓取台费使用记录到 ODS"),
    ("ODS_ASSISTANT_ACCOUNT", "助教账号", "抓取助教信息到 ODS"),
    ("ODS_ASSISTANT_LEDGER", "助教流水", "抓取助教服务流水到 ODS"),
    ("ODS_ASSISTANT_ABOLISH", "助教作废", "抓取助教作废记录到 ODS"),
    ("ODS_REFUND", "退款流水", "抓取退款记录到 ODS"),
    ("ODS_PLATFORM_COUPON", "平台券核销", "抓取券核销记录到 ODS"),
    ("ODS_RECHARGE_SETTLE", "充值结算", "抓取充值结算记录到 ODS"),
    ("ODS_GROUP_PACKAGE", "团购套餐", "抓取团购套餐定义到 ODS"),
    ("ODS_GROUP_BUY_REDEMPTION", "团购核销", "抓取团购核销记录到 ODS"),
    ("ODS_INVENTORY_STOCK", "库存汇总", "抓取库存汇总到 ODS"),
    ("ODS_INVENTORY_CHANGE", "库存变化", "抓取库存变动记录到 ODS"),
    ("ODS_TABLES", "台桌维表", "抓取台桌信息到 ODS"),
    ("ODS_GOODS_CATEGORY", "商品分类", "抓取商品分类到 ODS"),
    ("ODS_STORE_GOODS", "门店商品", "抓取门店商品档案到 ODS"),
    ("ODS_STORE_GOODS_SALES", "商品销售流水", "抓取商品销售流水到 ODS"),
    ("ODS_TABLE_FEE_DISCOUNT", "台费折扣", "抓取台费折扣记录到 ODS"),
    ("ODS_TENANT_GOODS", "租户商品", "抓取租户商品档案到 ODS"),
    ("ODS_SETTLEMENT_TICKET", "结账小票", "抓取结账小票详情到 ODS"),
    # DWD/DWS 任务
    ("DWD_LOAD_FROM_ODS", "ODS→DWD 装载", "从 ODS 增量装载到 DWD"),
    ("DWD_QUALITY_CHECK", "DWD 质量检查", "执行 DWD 数据质量检查"),
    ("DWS_BUILD_ORDER_SUMMARY", "构建订单汇总", "重算 DWS 订单汇总表"),
    # Schema 初始化
    ("INIT_ODS_SCHEMA", "初始化 ODS Schema", "创建/重建 ODS 表结构"),
    ("INIT_DWD_SCHEMA", "初始化 DWD Schema", "创建/重建 DWD 表结构"),
    ("INIT_DWS_SCHEMA", "初始化 DWS Schema", "创建/重建 DWS 表结构"),
    # 其他任务
    ("MANUAL_INGEST", "手工数据灌入", "从本地 JSON 回放入库"),
    ("CHECK_CUTOFF", "检查 Cutoff", "查看各表数据截止时间"),
    ("DATA_INTEGRITY_CHECK", "数据完整性检查", "检查 ODS/DWD 数据完整性"),
]


class TaskPanel(QWidget):
    """任务配置面板"""
    
    # 信号
    task_started = Signal(str)  # 任务开始信号
    task_finished = Signal(bool, str)  # 任务完成信号 (success, message)
    log_message = Signal(str)  # 日志消息信号
    add_to_queue = Signal(object)  # 添加到队列信号 (TaskConfig)
    create_schedule = Signal(str, list, dict)  # 创建调度任务信号 (name, task_codes, task_config)
    
    def __init__(self, parent=None):
        super().__init__(parent)
        self.cli_builder = CLIBuilder()
        self.worker = None
        self._init_ui()
        self._connect_signals()
        self.refresh_tasks()
        self._load_settings()  # 启动时加载保存的设置
    
    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)
        
        # 创建分割器
        splitter = QSplitter(Qt.Horizontal)
        layout.addWidget(splitter, 1)
        
        # 左侧：任务选择
        left_widget = self._create_task_selection()
        splitter.addWidget(left_widget)
        
        # 右侧：参数配置
        right_widget = self._create_config_area()
        splitter.addWidget(right_widget)
        
        # 设置分割比例
        splitter.setSizes([400, 600])
        
        # 底部：CLI 预览和执行按钮
        bottom_widget = self._create_bottom_area()
        layout.addWidget(bottom_widget)
    
    def _create_task_selection(self) -> QWidget:
        """创建任务选择区域"""
        widget = QWidget()
        layout = QVBoxLayout(widget)
        layout.setContentsMargins(0, 0, 8, 0)
        
        # 任务分类过滤
        filter_layout = QHBoxLayout()
        filter_layout.addWidget(QLabel("分类:"))
        self.category_combo = QComboBox()
        self.category_combo.addItem("全部", None)
        self.category_combo.addItem("ODS 数据抓取", TaskCategory.ODS)
        self.category_combo.addItem("DWD 装载", TaskCategory.DWD)
        self.category_combo.addItem("DWS 汇总", TaskCategory.DWS)
        self.category_combo.addItem("Schema 初始化", TaskCategory.SCHEMA)
        self.category_combo.addItem("质量检查", TaskCategory.QUALITY)
        self.category_combo.addItem("其他", TaskCategory.OTHER)
        filter_layout.addWidget(self.category_combo, 1)
        layout.addLayout(filter_layout)
        
        # 快捷操作按钮
        btn_layout = QHBoxLayout()
        self.select_all_btn = QPushButton("全选")
        self.select_all_btn.setProperty("secondary", True)
        self.deselect_all_btn = QPushButton("全不选")
        self.deselect_all_btn.setProperty("secondary", True)
        btn_layout.addWidget(self.select_all_btn)
        btn_layout.addWidget(self.deselect_all_btn)
        layout.addLayout(btn_layout)
        
        # 任务列表
        self.task_list = QListWidget()
        self.task_list.setSelectionMode(QListWidget.MultiSelection)
        layout.addWidget(self.task_list, 1)
        
        # 已选任务数
        self.selected_count_label = QLabel("已选: 0 个任务")
        self.selected_count_label.setProperty("subheading", True)
        layout.addWidget(self.selected_count_label)
        
        return widget
    
    def _create_config_area(self) -> QWidget:
        """创建参数配置区域"""
        # 使用选项卡区分快捷操作和高级配置
        tab_widget = QTabWidget()
        
        # 快捷操作选项卡
        quick_tab = self._create_quick_actions_tab()
        tab_widget.addTab(quick_tab, "快捷操作")
        
        # 高级配置选项卡
        advanced_tab = self._create_advanced_config_tab()
        tab_widget.addTab(advanced_tab, "高级配置")
        
        return tab_widget
    
    def _create_quick_actions_tab(self) -> QWidget:
        """创建快捷操作选项卡"""
        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)
        scroll_area.setFrameShape(QFrame.NoFrame)
        
        widget = QWidget()
        layout = QVBoxLayout(widget)
        layout.setContentsMargins(8, 8, 8, 8)
        layout.setSpacing(16)
        
        # ====== 自动更新 ======
        auto_update_group = QGroupBox("自动更新（API → ODS）")
        auto_update_layout = QVBoxLayout(auto_update_group)
        
        # 说明
        desc_label = QLabel("从 API 抓取数据并更新 ODS 层，适用于日常增量更新")
        desc_label.setProperty("subheading", True)
        desc_label.setWordWrap(True)
        auto_update_layout.addWidget(desc_label)
        
        # 时间范围
        time_layout = QHBoxLayout()
        time_layout.addWidget(QLabel("时间范围:"))
        
        self.auto_update_hours = QSpinBox()
        self.auto_update_hours.setRange(1, 720)  # 1小时 - 30天
        self.auto_update_hours.setValue(24)
        self.auto_update_hours.setSuffix(" 小时前")
        time_layout.addWidget(self.auto_update_hours)
        
        time_layout.addWidget(QLabel("到 当前"))
        time_layout.addStretch()
        auto_update_layout.addLayout(time_layout)
        
        # 冗余窗口
        overlap_layout = QHBoxLayout()
        overlap_layout.addWidget(QLabel("冗余窗口:"))
        self.overlap_seconds = QSpinBox()
        self.overlap_seconds.setRange(0, 7200)
        self.overlap_seconds.setValue(3600)
        self.overlap_seconds.setSuffix(" 秒")
        self.overlap_seconds.setToolTip("向前多抓取的时间，避免边界数据丢失")
        overlap_layout.addWidget(self.overlap_seconds)
        overlap_layout.addStretch()
        auto_update_layout.addLayout(overlap_layout)
        
        # 任务选择
        task_header_layout = QHBoxLayout()
        task_label = QLabel("包含任务:")
        task_header_layout.addWidget(task_label)
        task_header_layout.addStretch()
        
        self.auto_update_select_all_btn = QPushButton("全选")
        self.auto_update_select_all_btn.setProperty("secondary", True)
        self.auto_update_select_all_btn.setFixedWidth(60)
        task_header_layout.addWidget(self.auto_update_select_all_btn)
        
        self.auto_update_deselect_all_btn = QPushButton("全不选")
        self.auto_update_deselect_all_btn.setProperty("secondary", True)
        self.auto_update_deselect_all_btn.setFixedWidth(60)
        task_header_layout.addWidget(self.auto_update_deselect_all_btn)
        
        auto_update_layout.addLayout(task_header_layout)
        
        self.auto_update_tasks_list = QListWidget()
        self.auto_update_tasks_list.setSelectionMode(QListWidget.MultiSelection)
        self.auto_update_tasks_list.setMaximumHeight(150)
        for task_code in AUTO_UPDATE_TASKS:
            item = QListWidgetItem(task_code)
            item.setSelected(True)  # 默认全选
            self.auto_update_tasks_list.addItem(item)
        auto_update_layout.addWidget(self.auto_update_tasks_list)
        
        # 包含 DWD 装载
        self.include_dwd_check = QCheckBox("完成后执行 DWD 装载 (ODS → DWD)")
        self.include_dwd_check.setChecked(True)
        auto_update_layout.addWidget(self.include_dwd_check)
        
        # 自动校验
        self.auto_verify_check = QCheckBox("完成后执行数据校验")
        self.auto_verify_check.setToolTip("ETL 导入完成后，自动执行数据完整性校验")
        self.auto_verify_check.setChecked(False)
        auto_update_layout.addWidget(self.auto_verify_check)
        
        # 操作按钮
        auto_btn_layout = QHBoxLayout()
        auto_btn_layout.addStretch()
        
        self.auto_update_run_btn = QPushButton("立即执行一次")
        self.auto_update_run_btn.setToolTip("添加到任务队列并立即执行")
        auto_btn_layout.addWidget(self.auto_update_run_btn)
        
        self.auto_update_schedule_btn = QPushButton("创建调度任务")
        self.auto_update_schedule_btn.setToolTip("添加到定时调度中，可设置执行周期")
        auto_btn_layout.addWidget(self.auto_update_schedule_btn)
        
        auto_update_layout.addLayout(auto_btn_layout)
        
        layout.addWidget(auto_update_group)
        
        # ====== 数据校验 ======
        integrity_group = QGroupBox("数据校验（API vs ODS）")
        integrity_layout = QVBoxLayout(integrity_group)
        
        # 说明
        int_desc = QLabel("对比 API 数据与 ODS 数据，检查是否有缺失或不一致")
        int_desc.setProperty("subheading", True)
        int_desc.setWordWrap(True)
        integrity_layout.addWidget(int_desc)
        
        # 校验模式
        mode_layout = QHBoxLayout()
        mode_layout.addWidget(QLabel("校验模式:"))
        self.integrity_mode_combo = QComboBox()
        self.integrity_mode_combo.addItem("历史全量校验", "history")
        self.integrity_mode_combo.addItem("最近增量校验", "recent")
        mode_layout.addWidget(self.integrity_mode_combo, 1)
        integrity_layout.addLayout(mode_layout)
        
        # 时间范围 - 历史模式
        self.history_range_widget = QWidget()
        history_layout = QGridLayout(self.history_range_widget)
        history_layout.setContentsMargins(0, 0, 0, 0)
        
        history_layout.addWidget(QLabel("开始日期:"), 0, 0)
        self.integrity_start_date = QDateTimeEdit()
        self.integrity_start_date.setDisplayFormat("yyyy-MM-dd")
        self.integrity_start_date.setCalendarPopup(True)
        self.integrity_start_date.setDateTime(QDateTime.currentDateTime().addMonths(-6))
        history_layout.addWidget(self.integrity_start_date, 0, 1)
        
        history_layout.addWidget(QLabel("结束日期:"), 1, 0)
        self.integrity_end_date = QDateTimeEdit()
        self.integrity_end_date.setDisplayFormat("yyyy-MM-dd")
        self.integrity_end_date.setCalendarPopup(True)
        self.integrity_end_date.setDateTime(QDateTime.currentDateTime())
        history_layout.addWidget(self.integrity_end_date, 1, 1)
        
        integrity_layout.addWidget(self.history_range_widget)
        
        # 时间范围 - 最近模式
        self.recent_range_widget = QWidget()
        recent_layout = QHBoxLayout(self.recent_range_widget)
        recent_layout.setContentsMargins(0, 0, 0, 0)
        recent_layout.addWidget(QLabel("回溯时间:"))
        self.integrity_hours = QSpinBox()
        self.integrity_hours.setRange(1, 168)  # 1小时 - 7天
        self.integrity_hours.setValue(24)
        self.integrity_hours.setSuffix(" 小时")
        recent_layout.addWidget(self.integrity_hours)
        recent_layout.addStretch()
        self.recent_range_widget.setVisible(False)
        integrity_layout.addWidget(self.recent_range_widget)
        
        # 校验选项
        self.include_dimensions_check = QCheckBox("包含维度表校验")
        integrity_layout.addWidget(self.include_dimensions_check)
        
        # 自动补全选项
        self.auto_backfill_check = QCheckBox("更新并验证（自动回补缺失/不一致）")
        self.auto_backfill_check.setToolTip("先校验，再自动从 API 回补缺失/不一致数据；可通过 INTEGRITY_RECHECK_AFTER_BACKFILL 控制是否再校验")
        integrity_layout.addWidget(self.auto_backfill_check)
        
        # 指定 ODS 任务
        ods_task_layout = QHBoxLayout()
        ods_task_layout.addWidget(QLabel("指定任务 (可选):"))
        self.integrity_ods_tasks = QLineEdit()
        self.integrity_ods_tasks.setPlaceholderText("例: ODS_PAYMENT,ODS_MEMBER (留空=全部)")
        ods_task_layout.addWidget(self.integrity_ods_tasks, 1)
        integrity_layout.addLayout(ods_task_layout)
        
        # 操作按钮
        int_btn_layout = QHBoxLayout()
        int_btn_layout.addStretch()
        
        self.integrity_run_btn = QPushButton("立即执行一次")
        self.integrity_run_btn.setToolTip("添加到任务队列并立即执行")
        int_btn_layout.addWidget(self.integrity_run_btn)
        
        self.integrity_schedule_btn = QPushButton("创建调度任务")
        self.integrity_schedule_btn.setToolTip("添加到定时调度中，可设置执行周期")
        int_btn_layout.addWidget(self.integrity_schedule_btn)
        
        integrity_layout.addLayout(int_btn_layout)
        
        layout.addWidget(integrity_group)
        
        # 弹性空间
        layout.addStretch()
        
        scroll_area.setWidget(widget)
        return scroll_area
    
    def _create_advanced_config_tab(self) -> QWidget:
        """创建高级配置选项卡"""
        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)
        scroll_area.setFrameShape(QFrame.NoFrame)
        
        widget = QWidget()
        layout = QVBoxLayout(widget)
        layout.setContentsMargins(8, 8, 8, 8)
        
        # Pipeline 流程配置
        pipeline_group = QGroupBox("流水线配置")
        pipeline_layout = QGridLayout(pipeline_group)
        
        pipeline_layout.addWidget(QLabel("运行模式:"), 0, 0)
        self.pipeline_flow_combo = QComboBox()
        self.pipeline_flow_combo.addItem("FULL - 在线抓取 + 入库", "FULL")
        self.pipeline_flow_combo.addItem("FETCH_ONLY - 仅在线抓取落盘", "FETCH_ONLY")
        self.pipeline_flow_combo.addItem("INGEST_ONLY - 仅本地 JSON 入库", "INGEST_ONLY")
        pipeline_layout.addWidget(self.pipeline_flow_combo, 0, 1)
        
        self.dry_run_check = QCheckBox("Dry-run 模式（不提交数据库）")
        pipeline_layout.addWidget(self.dry_run_check, 1, 0, 1, 2)
        
        layout.addWidget(pipeline_group)
        
        # 时间窗口配置
        window_group = QGroupBox("时间窗口（可选）")
        window_layout = QGridLayout(window_group)
        
        window_layout.addWidget(QLabel("开始时间:"), 0, 0)
        self.window_start_edit = QLineEdit()
        self.window_start_edit.setPlaceholderText("例: 2025-07-01 00:00:00")
        window_layout.addWidget(self.window_start_edit, 0, 1)
        
        window_layout.addWidget(QLabel("结束时间:"), 1, 0)
        self.window_end_edit = QLineEdit()
        self.window_end_edit.setPlaceholderText("例: 2025-08-01 00:00:00")
        window_layout.addWidget(self.window_end_edit, 1, 1)
        
        # 窗口切分选项
        window_layout.addWidget(QLabel("切分模式:"), 2, 0)
        self.window_split_combo = QComboBox()
        self.window_split_combo.addItem("不切分", "none")
        self.window_split_combo.addItem("按月切分", "month")
        self.window_split_combo.setToolTip("长时间窗口按月切分执行，避免单次请求过大")
        window_layout.addWidget(self.window_split_combo, 2, 1)
        
        window_layout.addWidget(QLabel("补偿小时:"), 3, 0)
        self.window_compensation_spin = QSpinBox()
        self.window_compensation_spin.setRange(0, 168)  # 最多7天
        self.window_compensation_spin.setValue(0)
        self.window_compensation_spin.setSuffix(" 小时")
        self.window_compensation_spin.setToolTip("窗口前后扩展的小时数，用于捕获边界数据")
        window_layout.addWidget(self.window_compensation_spin, 3, 1)
        
        layout.addWidget(window_group)
        
        # 数据源配置
        source_group = QGroupBox("数据源配置（INGEST_ONLY 模式）")
        source_layout = QGridLayout(source_group)
        
        source_layout.addWidget(QLabel("JSON 目录:"), 0, 0)
        self.ingest_source_edit = QLineEdit()
        self.ingest_source_edit.setPlaceholderText("本地 JSON 文件目录")
        source_layout.addWidget(self.ingest_source_edit, 0, 1)
        
        self.browse_btn = QPushButton("浏览...")
        self.browse_btn.setProperty("secondary", True)
        source_layout.addWidget(self.browse_btn, 0, 2)
        
        layout.addWidget(source_group)
        
        # 覆盖配置（高级）
        override_group = QGroupBox("覆盖配置（可选）")
        override_layout = QGridLayout(override_group)
        
        override_layout.addWidget(QLabel("门店 ID:"), 0, 0)
        self.store_id_edit = QLineEdit()
        self.store_id_edit.setPlaceholderText("使用 .env 中的配置")
        override_layout.addWidget(self.store_id_edit, 0, 1)
        
        override_layout.addWidget(QLabel("数据库 DSN:"), 1, 0)
        self.pg_dsn_edit = QLineEdit()
        self.pg_dsn_edit.setPlaceholderText("使用 .env 中的配置")
        override_layout.addWidget(self.pg_dsn_edit, 1, 1)
        
        override_layout.addWidget(QLabel("API Token:"), 2, 0)
        self.api_token_edit = QLineEdit()
        self.api_token_edit.setPlaceholderText("使用 .env 中的配置")
        self.api_token_edit.setEchoMode(QLineEdit.Password)
        override_layout.addWidget(self.api_token_edit, 2, 1)
        
        layout.addWidget(override_group)
        
        # 弹性空间
        layout.addStretch()
        
        scroll_area.setWidget(widget)
        return scroll_area
    
    def _create_bottom_area(self) -> QWidget:
        """创建底部区域"""
        widget = QWidget()
        layout = QVBoxLayout(widget)
        layout.setContentsMargins(0, 0, 0, 0)
        
        # CLI 预览
        preview_group = QGroupBox("命令行预览")
        preview_layout = QVBoxLayout(preview_group)
        
        self.cli_preview = QPlainTextEdit()
        self.cli_preview.setReadOnly(True)
        self.cli_preview.setMaximumHeight(80)
        self.cli_preview.setFont(QFont("Consolas", 10))
        preview_layout.addWidget(self.cli_preview)
        
        layout.addWidget(preview_group)
        
        # 执行按钮
        btn_layout = QHBoxLayout()
        btn_layout.addStretch()
        
        self.add_to_queue_btn = QPushButton("添加到队列")
        self.add_to_queue_btn.setProperty("secondary", True)
        btn_layout.addWidget(self.add_to_queue_btn)
        
        self.run_btn = QPushButton("立即执行")
        self.run_btn.setFixedWidth(120)
        btn_layout.addWidget(self.run_btn)
        
        self.stop_btn = QPushButton("停止")
        self.stop_btn.setProperty("danger", True)
        self.stop_btn.setEnabled(False)
        self.stop_btn.setFixedWidth(80)
        btn_layout.addWidget(self.stop_btn)
        
        layout.addLayout(btn_layout)
        
        return widget
    
    def _connect_signals(self):
        """连接信号"""
        # 分类过滤
        self.category_combo.currentIndexChanged.connect(self._filter_tasks)
        
        # 任务选择
        self.task_list.itemSelectionChanged.connect(self._on_selection_changed)
        self.select_all_btn.clicked.connect(self._select_all)
        self.deselect_all_btn.clicked.connect(self._deselect_all)
        
        # 配置变化
        self.pipeline_flow_combo.currentIndexChanged.connect(self._update_preview)
        self.dry_run_check.stateChanged.connect(self._update_preview)
        self.window_start_edit.textChanged.connect(self._update_preview)
        self.window_end_edit.textChanged.connect(self._update_preview)
        self.ingest_source_edit.textChanged.connect(self._update_preview)
        self.store_id_edit.textChanged.connect(self._update_preview)
        self.pg_dsn_edit.textChanged.connect(self._update_preview)
        self.api_token_edit.textChanged.connect(self._update_preview)
        
        # 浏览目录
        self.browse_btn.clicked.connect(self._browse_source_dir)
        
        # 执行按钮
        self.run_btn.clicked.connect(self._run_task)
        self.stop_btn.clicked.connect(self._stop_task)
        self.add_to_queue_btn.clicked.connect(self._add_task_to_queue)
        
        # 快捷操作 - 自动更新
        self.auto_update_run_btn.clicked.connect(self._run_auto_update_now)
        self.auto_update_schedule_btn.clicked.connect(self._create_auto_update_schedule)
        self.auto_update_select_all_btn.clicked.connect(self._auto_update_select_all)
        self.auto_update_deselect_all_btn.clicked.connect(self._auto_update_deselect_all)
        
        # 快捷操作 - 数据校验
        self.integrity_run_btn.clicked.connect(self._run_integrity_check_now)
        self.integrity_schedule_btn.clicked.connect(self._create_integrity_schedule)
        self.integrity_mode_combo.currentIndexChanged.connect(self._on_integrity_mode_changed)
        
        # 保存设置的信号连接
        self.auto_update_hours.valueChanged.connect(self._save_auto_update_settings)
        self.overlap_seconds.valueChanged.connect(self._save_auto_update_settings)
        self.include_dwd_check.stateChanged.connect(self._save_auto_update_settings)
        self.auto_verify_check.stateChanged.connect(self._save_auto_update_settings)
        self.integrity_mode_combo.currentIndexChanged.connect(self._save_integrity_settings)
        self.integrity_start_date.dateTimeChanged.connect(self._save_integrity_settings)
        self.integrity_end_date.dateTimeChanged.connect(self._save_integrity_settings)
        self.integrity_hours.valueChanged.connect(self._save_integrity_settings)
        self.include_dimensions_check.stateChanged.connect(self._save_integrity_settings)
        self.auto_backfill_check.stateChanged.connect(self._save_integrity_settings)
        self.integrity_ods_tasks.textChanged.connect(self._save_integrity_settings)
        self.pipeline_flow_combo.currentIndexChanged.connect(self._save_advanced_settings)
        self.dry_run_check.stateChanged.connect(self._save_advanced_settings)
        self.window_split_combo.currentIndexChanged.connect(self._save_advanced_settings)
        self.window_compensation_spin.valueChanged.connect(self._save_advanced_settings)
    
    def refresh_tasks(self):
        """刷新任务列表"""
        self.task_list.clear()
        
        current_category = self.category_combo.currentData()
        
        for code, name, desc in ALL_TASKS:
            category = get_task_category(code)
            
            # 应用分类过滤
            if current_category is not None and category != current_category:
                continue
            
            item = QListWidgetItem(f"{name} ({code})")
            item.setData(Qt.UserRole, code)
            item.setToolTip(desc)
            self.task_list.addItem(item)
        
        self._on_selection_changed()
    
    def _filter_tasks(self):
        """过滤任务列表"""
        self.refresh_tasks()
    
    def _on_selection_changed(self):
        """选择变化时"""
        selected = self.task_list.selectedItems()
        self.selected_count_label.setText(f"已选: {len(selected)} 个任务")
        self._update_preview()
    
    def _select_all(self):
        """全选"""
        self.task_list.selectAll()
    
    def _deselect_all(self):
        """全不选"""
        self.task_list.clearSelection()
    
    def _browse_source_dir(self):
        """浏览数据源目录"""
        dir_path = QFileDialog.getExistingDirectory(
            self, "选择 JSON 数据目录"
        )
        if dir_path:
            self.ingest_source_edit.setText(dir_path)
    
    def _get_config(self) -> TaskConfig:
        """获取当前配置"""
        # 获取选中的任务
        selected_tasks = []
        for item in self.task_list.selectedItems():
            task_code = item.data(Qt.UserRole)
            if task_code:
                selected_tasks.append(task_code)
        
        # 构建环境变量（窗口切分参数）
        env_vars = {}
        split_unit = self.window_split_combo.currentData() or "none"
        compensation = self.window_compensation_spin.value()
        if split_unit and split_unit != "none":
            env_vars["WINDOW_SPLIT_UNIT"] = split_unit
        if compensation > 0:
            env_vars["WINDOW_COMPENSATION_HOURS"] = str(compensation)
        
        # 构建配置
        config = TaskConfig(
            tasks=selected_tasks,
            pipeline_flow=self.pipeline_flow_combo.currentData(),
            dry_run=self.dry_run_check.isChecked(),
            window_start=self.window_start_edit.text().strip() or None,
            window_end=self.window_end_edit.text().strip() or None,
            window_split=split_unit,
            window_compensation=compensation,
            ingest_source=self.ingest_source_edit.text().strip() or None,
            store_id=int(self.store_id_edit.text()) if self.store_id_edit.text().strip().isdigit() else None,
            pg_dsn=self.pg_dsn_edit.text().strip() or None,
            api_token=self.api_token_edit.text().strip() or None,
            env_vars=env_vars,
        )
        
        return config
    
    def _update_preview(self):
        """更新命令行预览"""
        config = self._get_config()
        cmd_str = self.cli_builder.build_command_string(config)
        self.cli_preview.setPlainText(cmd_str)
    
    def _run_task(self):
        """执行任务"""
        config = self._get_config()
        
        if not config.tasks:
            QMessageBox.warning(self, "提示", "请至少选择一个任务")
            return
        
        # 创建工作线程
        cmd = self.cli_builder.build_command(config)
        self.worker = TaskWorker(cmd)
        
        # 连接信号
        self.worker.output_received.connect(self._on_output)
        self.worker.task_finished.connect(self._on_finished)
        self.worker.error_occurred.connect(self._on_error)
        
        # 更新 UI 状态
        self.run_btn.setEnabled(False)
        self.stop_btn.setEnabled(True)
        
        # 发送开始信号
        task_info = ",".join(config.tasks[:3])
        if len(config.tasks) > 3:
            task_info += f" 等{len(config.tasks)}个任务"
        self.task_started.emit(task_info)
        
        # 启动
        self.worker.start()
    
    def _stop_task(self):
        """停止任务"""
        if self.worker and self.worker.isRunning():
            self.worker.stop()
            self.log_message.emit("[GUI] 正在停止任务...")
    
    def _on_output(self, line: str):
        """收到输出"""
        self.log_message.emit(line)
    
    def _on_finished(self, exit_code: int, summary: str):
        """任务完成"""
        self.run_btn.setEnabled(True)
        self.stop_btn.setEnabled(False)
        
        success = exit_code == 0
        message = summary if summary else ("任务执行成功" if success else f"任务执行失败 (exit={exit_code})")
        self.task_finished.emit(success, message)
        
        if success:
            self.log_message.emit(f"[GUI] 任务完成: {message}")
        else:
            self.log_message.emit(f"[GUI] 任务失败: {message}")
    
    def _on_error(self, error: str):
        """发生错误"""
        self.log_message.emit(f"[GUI] 错误: {error}")
        QMessageBox.critical(self, "执行错误", error)
    
    def is_running(self) -> bool:
        """是否正在执行任务"""
        return self.worker is not None and self.worker.isRunning()
    
    def _add_task_to_queue(self):
        """将任务列表中选中的任务添加到队列"""
        config = self._get_config()
        
        if not config.tasks:
            QMessageBox.warning(self, "提示", "请至少选择一个任务")
            return
        
        # 发送信号添加到队列
        self.add_to_queue.emit(config)
        
        task_info = ",".join(config.tasks[:3])
        if len(config.tasks) > 3:
            task_info += f" 等{len(config.tasks)}个"
        
        self.log_message.emit(f"[GUI] 已添加到任务队列: {task_info}")
        QMessageBox.information(self, "提示", f"已添加到任务队列\n\n任务: {task_info}\n\n请切换到「任务管理」查看和执行")
    
    def _on_integrity_mode_changed(self, index: int):
        """校验模式变化"""
        mode = self.integrity_mode_combo.currentData()
        self.history_range_widget.setVisible(mode == "history")
        self.recent_range_widget.setVisible(mode == "recent")
    
    def _auto_update_select_all(self):
        """自动更新任务列表全选"""
        self.auto_update_tasks_list.selectAll()
        self._save_auto_update_settings()
    
    def _auto_update_deselect_all(self):
        """自动更新任务列表全不选"""
        self.auto_update_tasks_list.clearSelection()
        self._save_auto_update_settings()
    
    def _get_auto_update_tasks(self) -> list:
        """获取自动更新选中的任务"""
        selected_tasks = []
        for i in range(self.auto_update_tasks_list.count()):
            item = self.auto_update_tasks_list.item(i)
            if item.isSelected():
                selected_tasks.append(item.text())
        
        if self.include_dwd_check.isChecked():
            selected_tasks.append("DWD_LOAD_FROM_ODS")
        
        return selected_tasks
    
    def _get_auto_update_config(self) -> dict:
        """获取自动更新配置"""
        return {
            "pipeline_flow": "FULL",
            "lookback_hours": self.auto_update_hours.value(),
            "overlap_seconds": self.overlap_seconds.value(),
        }
    
    def _run_auto_update_now(self):
        """立即执行自动更新（添加到队列）"""
        selected_tasks = self._get_auto_update_tasks()
        
        if not selected_tasks:
            QMessageBox.warning(self, "提示", "请至少选择一个任务")
            return
        
        hours = self.auto_update_hours.value()
        overlap = self.overlap_seconds.value()
        include_dwd = self.include_dwd_check.isChecked()
        auto_verify = self.auto_verify_check.isChecked()
        
        now = datetime.now()
        start_time = now - timedelta(hours=hours, seconds=overlap)
        
        # 构建完整任务列表
        all_tasks = selected_tasks.copy()
        
        # 添加 DWD 装载
        if include_dwd and "DWD_LOAD_FROM_ODS" not in all_tasks:
            all_tasks.append("DWD_LOAD_FROM_ODS")
        
        # 添加数据校验
        if auto_verify and "DATA_INTEGRITY_CHECK" not in all_tasks:
            all_tasks.append("DATA_INTEGRITY_CHECK")
        
        # 构建环境变量（用于校验任务）
        env_vars = {}
        if auto_verify:
            env_vars["INTEGRITY_MODE"] = "window"
            env_vars["INTEGRITY_AUTO_BACKFILL"] = "1"  # 自动补全丢失数据
        
        config = TaskConfig(
            tasks=all_tasks,
            pipeline_flow="FULL",
            window_start=start_time.strftime("%Y-%m-%d %H:%M:%S"),
            window_end=now.strftime("%Y-%m-%d %H:%M:%S"),
            env_vars=env_vars,
        )
        
        # 更新预览
        cmd_str = self.cli_builder.build_command_string(config)
        self.cli_preview.setPlainText(cmd_str)
        
        # 构建任务描述
        desc_parts = [f"自动更新 ({hours}h)"]
        if include_dwd:
            desc_parts.append("+ DWD")
        if auto_verify:
            desc_parts.append("+ 校验")
        task_desc = " ".join(desc_parts)
        
        # 发送信号添加到队列
        self.add_to_queue.emit(config)
        self.log_message.emit(f"[GUI] 已添加到任务队列: {task_desc}")
        QMessageBox.information(self, "提示", f"已添加到任务队列\n\n任务: {task_desc}\n包含: {len(all_tasks)} 个任务\n\n请切换到「任务管理」查看和执行")
    
    def _create_auto_update_schedule(self):
        """创建自动更新的调度任务"""
        selected_tasks = self._get_auto_update_tasks()
        
        if not selected_tasks:
            QMessageBox.warning(self, "提示", "请至少选择一个任务")
            return
        
        hours = self.auto_update_hours.value()
        task_config = self._get_auto_update_config()
        
        # 发送信号创建调度任务
        self.create_schedule.emit(
            f"自动更新 ({hours}h)",
            selected_tasks,
            task_config
        )
        self.log_message.emit(f"[GUI] 创建调度任务: 自动更新 ({hours}h)")
    
    def _get_integrity_config(self) -> dict:
        """获取数据校验配置"""
        mode = self.integrity_mode_combo.currentData()
        
        config = {
            "pipeline_flow": "INGEST_ONLY",
            "integrity_mode": mode,
            "integrity_include_dimensions": self.include_dimensions_check.isChecked(),
            "integrity_auto_backfill": self.auto_backfill_check.isChecked(),
        }
        
        if mode == "history":
            config["integrity_history_start"] = self.integrity_start_date.dateTime().toString("yyyy-MM-dd")
            config["integrity_history_end"] = self.integrity_end_date.dateTime().toString("yyyy-MM-dd")
        else:
            config["lookback_hours"] = self.integrity_hours.value()
        
        ods_tasks = self.integrity_ods_tasks.text().strip()
        if ods_tasks:
            config["integrity_ods_task_codes"] = ods_tasks
        
        return config
    
    def _run_integrity_check_now(self):
        """立即执行数据校验（添加到队列）"""
        mode = self.integrity_mode_combo.currentData()
        int_config = self._get_integrity_config()
        
        # 通过环境变量传递 integrity 配置（CLI 不支持这些参数）
        env_vars = {
            "INTEGRITY_MODE": int_config.get("integrity_mode", "history"),
            "INTEGRITY_INCLUDE_DIMENSIONS": "1" if int_config.get("integrity_include_dimensions") else "0",
            "INTEGRITY_AUTO_BACKFILL": "1" if int_config.get("integrity_auto_backfill") else "0",
        }
        
        if mode == "history":
            env_vars["INTEGRITY_HISTORY_START"] = int_config.get("integrity_history_start")
            env_vars["INTEGRITY_HISTORY_END"] = int_config.get("integrity_history_end")
            desc = f"数据校验 ({int_config.get('integrity_history_start')} ~ {int_config.get('integrity_history_end')})"
        else:
            hours = int_config.get("lookback_hours", 24)
            now = datetime.now()
            start_time = now - timedelta(hours=hours)
            env_vars["INTEGRITY_HISTORY_START"] = start_time.strftime("%Y-%m-%d")
            env_vars["INTEGRITY_HISTORY_END"] = now.strftime("%Y-%m-%d")
            desc = f"数据校验 (最近 {hours}h)"
        
        if int_config.get("integrity_ods_task_codes"):
            env_vars["INTEGRITY_ODS_TASK_CODES"] = int_config.get("integrity_ods_task_codes")
        
        if int_config.get("integrity_auto_backfill"):
            desc += " + 自动补全"
        
        config = TaskConfig(
            tasks=["DATA_INTEGRITY_CHECK"],
            pipeline_flow="FULL",  # 使用 FULL 因为需要 DB 连接
            env_vars=env_vars,
        )
        
        # 更新预览
        cmd_str = self.cli_builder.build_command_string(config)
        self.cli_preview.setPlainText(cmd_str + f"\n\n# 环境变量:\n" + "\n".join(f"# {k}={v}" for k, v in env_vars.items()))
        
        # 发送信号添加到队列
        self.add_to_queue.emit(config)
        self.log_message.emit(f"[GUI] 已添加到任务队列: {desc}")
        QMessageBox.information(self, "提示", f"已添加到任务队列\n\n任务: {desc}\n\n请切换到「任务管理」查看和执行")
    
    def _create_integrity_schedule(self):
        """创建数据校验的调度任务"""
        mode = self.integrity_mode_combo.currentData()
        task_config = self._get_integrity_config()
        
        if mode == "history":
            desc = f"数据校验 ({task_config.get('integrity_history_start')} ~ {task_config.get('integrity_history_end')})"
        else:
            hours = task_config.get("lookback_hours", 24)
            desc = f"数据校验 (最近 {hours}h)"
        
        # 发送信号创建调度任务
        self.create_schedule.emit(
            desc,
            ["DATA_INTEGRITY_CHECK"],
            task_config
        )
        self.log_message.emit(f"[GUI] 创建调度任务: {desc}")
    
    # ==================== 设置持久化 ====================
    
    def _load_settings(self):
        """从持久化存储加载设置"""
        try:
            # 加载自动更新设置
            self.auto_update_hours.setValue(app_settings.auto_update_hours)
            self.overlap_seconds.setValue(app_settings.auto_update_overlap_seconds)
            self.include_dwd_check.setChecked(app_settings.auto_update_include_dwd)
            self.auto_verify_check.setChecked(app_settings.auto_update_auto_verify)
            
            # 恢复自动更新任务选择
            saved_tasks = app_settings.auto_update_selected_tasks
            if saved_tasks:
                for i in range(self.auto_update_tasks_list.count()):
                    item = self.auto_update_tasks_list.item(i)
                    item.setSelected(item.text() in saved_tasks)
            
            # 加载数据校验设置
            mode = app_settings.integrity_mode
            mode_index = 0 if mode == "history" else 1
            self.integrity_mode_combo.setCurrentIndex(mode_index)
            
            # 历史日期
            if app_settings.integrity_history_start:
                try:
                    start_date = QDateTime.fromString(app_settings.integrity_history_start, "yyyy-MM-dd")
                    if start_date.isValid():
                        self.integrity_start_date.setDateTime(start_date)
                except Exception:
                    pass
            
            if app_settings.integrity_history_end:
                try:
                    end_date = QDateTime.fromString(app_settings.integrity_history_end, "yyyy-MM-dd")
                    if end_date.isValid():
                        self.integrity_end_date.setDateTime(end_date)
                except Exception:
                    pass
            
            self.integrity_hours.setValue(app_settings.integrity_lookback_hours)
            self.include_dimensions_check.setChecked(app_settings.integrity_include_dimensions)
            self.auto_backfill_check.setChecked(app_settings.integrity_auto_backfill)
            self.integrity_ods_tasks.setText(app_settings.integrity_ods_tasks)
            
            # 加载高级设置
            pipeline_flow = app_settings.advanced_pipeline_flow
            flow_map = {"FULL": 0, "FETCH_ONLY": 1, "INGEST_ONLY": 2}
            self.pipeline_flow_combo.setCurrentIndex(flow_map.get(pipeline_flow, 0))
            self.dry_run_check.setChecked(app_settings.advanced_dry_run)
            self.window_start_edit.setText(app_settings.advanced_window_start)
            self.window_end_edit.setText(app_settings.advanced_window_end)
            self.ingest_source_edit.setText(app_settings.advanced_ingest_source)
            
            # 加载窗口切分设置
            split_map = {"none": 0, "month": 1}
            self.window_split_combo.setCurrentIndex(split_map.get(app_settings.advanced_window_split, 0))
            self.window_compensation_spin.setValue(app_settings.advanced_window_compensation)
            
            # 更新 UI 状态
            self._on_integrity_mode_changed(mode_index)
            
        except Exception as e:
            # 加载失败时不影响程序运行
            print(f"加载设置失败: {e}")
    
    def _save_auto_update_settings(self):
        """保存自动更新设置"""
        try:
            app_settings.auto_update_hours = self.auto_update_hours.value()
            app_settings.auto_update_overlap_seconds = self.overlap_seconds.value()
            app_settings.auto_update_include_dwd = self.include_dwd_check.isChecked()
            app_settings.auto_update_auto_verify = self.auto_verify_check.isChecked()
            
            # 保存选中的任务
            selected_tasks = []
            for i in range(self.auto_update_tasks_list.count()):
                item = self.auto_update_tasks_list.item(i)
                if item.isSelected():
                    selected_tasks.append(item.text())
            app_settings.auto_update_selected_tasks = selected_tasks
        except Exception as e:
            print(f"保存自动更新设置失败: {e}")
    
    def _save_integrity_settings(self):
        """保存数据校验设置"""
        try:
            mode = self.integrity_mode_combo.currentData()
            app_settings.integrity_mode = mode or "history"
            app_settings.integrity_history_start = self.integrity_start_date.dateTime().toString("yyyy-MM-dd")
            app_settings.integrity_history_end = self.integrity_end_date.dateTime().toString("yyyy-MM-dd")
            app_settings.integrity_lookback_hours = self.integrity_hours.value()
            app_settings.integrity_include_dimensions = self.include_dimensions_check.isChecked()
            app_settings.integrity_auto_backfill = self.auto_backfill_check.isChecked()
            app_settings.integrity_ods_tasks = self.integrity_ods_tasks.text().strip()
        except Exception as e:
            print(f"保存数据校验设置失败: {e}")
    
    def _save_advanced_settings(self):
        """保存高级设置"""
        try:
            app_settings.advanced_pipeline_flow = self.pipeline_flow_combo.currentData() or "FULL"
            app_settings.advanced_dry_run = self.dry_run_check.isChecked()
            app_settings.advanced_window_start = self.window_start_edit.text().strip()
            app_settings.advanced_window_end = self.window_end_edit.text().strip()
            app_settings.advanced_ingest_source = self.ingest_source_edit.text().strip()
            app_settings.advanced_window_split = self.window_split_combo.currentData() or "none"
            app_settings.advanced_window_compensation = self.window_compensation_spin.value()
        except Exception as e:
            print(f"保存高级设置失败: {e}")
