# -*- 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, QTimer
from PySide6.QtGui import QFont

from ..models.task_model import TaskItem, TaskConfig, TaskCategory, TASK_CATEGORIES, get_task_category
from ..models.task_registry import (
    task_registry, get_all_task_tuples, get_fact_ods_task_codes,
    get_dimension_ods_task_codes, BusinessDomain, DOMAIN_LABELS
)
from ..utils.cli_builder import CLIBuilder
from ..utils.app_settings import app_settings
from ..workers.task_worker import TaskWorker
from .task_selector import TaskSelectorWidget


def _get_all_tasks():
    """从注册表动态获取所有任务"""
    return get_all_task_tuples()


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


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()  # 启动时加载保存的设置
        
        # 定时器：每秒更新时间预览
        self._time_preview_timer = QTimer(self)
        self._time_preview_timer.timeout.connect(self._update_sync_time_preview)
        self._time_preview_timer.start(1000)  # 每秒更新
    
    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(12)
        
        # ====== 1. 日常增量同步 ======
        sync_group = QGroupBox("日常增量同步")
        sync_layout = QVBoxLayout(sync_group)
        sync_layout.setSpacing(8)
        
        # 说明
        sync_desc = QLabel("从 API 抓取最新数据到 ODS，然后装载到 DWD。适用于日常增量更新、数据修复和历史补全。")
        sync_desc.setProperty("subheading", True)
        sync_desc.setWordWrap(True)
        sync_layout.addWidget(sync_desc)
        
        # 时间范围
        sync_time_layout = QHBoxLayout()
        sync_time_layout.addWidget(QLabel("回溯时间:"))
        self.sync_hours = QSpinBox()
        self.sync_hours.setRange(1, 720)
        self.sync_hours.setValue(24)
        self.sync_hours.setSuffix(" 小时")
        self.sync_hours.setToolTip("从当前时间向前回溯的小时数")
        sync_time_layout.addWidget(self.sync_hours)
        
        sync_time_layout.addWidget(QLabel("冗余:"))
        self.sync_overlap = QSpinBox()
        self.sync_overlap.setRange(0, 7200)
        self.sync_overlap.setValue(3600)
        self.sync_overlap.setSuffix(" 秒")
        self.sync_overlap.setToolTip("额外向前多抓取的时间，避免边界数据丢失")
        sync_time_layout.addWidget(self.sync_overlap)
        sync_time_layout.addStretch()
        sync_layout.addLayout(sync_time_layout)
        
        # 时间范围预览（精确到秒）
        self.sync_time_preview = QLabel()
        self.sync_time_preview.setStyleSheet("color: #666; font-size: 12px;")
        sync_layout.addWidget(self.sync_time_preview)
        self._update_sync_time_preview()
        
        # 任务选择（使用新组件）
        sync_task_label = QLabel("选择 ODS 任务（按业务域）:")
        sync_layout.addWidget(sync_task_label)
        
        self.sync_task_selector = TaskSelectorWidget(
            show_dimensions=True,
            show_facts=True,
            default_select_facts=True,
            default_select_dimensions=True,  # 默认包含维度表
            compact=True,
            max_height=200,
        )
        sync_layout.addWidget(self.sync_task_selector)
        
        # 附加选项
        sync_options_layout = QHBoxLayout()
        self.sync_include_dwd = QCheckBox("ODS 完成后装载 DWD")
        self.sync_include_dwd.setChecked(True)
        self.sync_include_dwd.setToolTip("ODS 抓取完成后自动执行 DWD 装载任务")
        sync_options_layout.addWidget(self.sync_include_dwd)
        
        self.sync_auto_verify = QCheckBox("完成后校验数据")
        self.sync_auto_verify.setChecked(False)
        self.sync_auto_verify.setToolTip("全部完成后执行数据完整性校验")
        sync_options_layout.addWidget(self.sync_auto_verify)
        sync_options_layout.addStretch()
        sync_layout.addLayout(sync_options_layout)
        
        # 操作按钮
        sync_btn_layout = QHBoxLayout()
        sync_btn_layout.addStretch()
        self.sync_run_btn = QPushButton("立即执行")
        self.sync_run_btn.setToolTip("添加到任务队列并执行")
        sync_btn_layout.addWidget(self.sync_run_btn)
        self.sync_schedule_btn = QPushButton("创建调度")
        self.sync_schedule_btn.setProperty("secondary", True)
        self.sync_schedule_btn.setToolTip("创建定时调度任务")
        sync_btn_layout.addWidget(self.sync_schedule_btn)
        sync_layout.addLayout(sync_btn_layout)
        
        layout.addWidget(sync_group)
        
        # ====== 2. 全量重刷 ======
        reload_group = QGroupBox("全量重刷")
        reload_layout = QVBoxLayout(reload_group)
        reload_layout.setSpacing(8)
        
        # 说明
        reload_desc = QLabel("指定精确时间窗口，重新抓取 ODS 数据并装载到 DWD。适用于大范围历史数据重刷、跨月补跑等场景。")
        reload_desc.setProperty("subheading", True)
        reload_desc.setWordWrap(True)
        reload_layout.addWidget(reload_desc)
        
        # 时间窗口
        reload_time_layout = QGridLayout()
        reload_time_layout.setColumnStretch(1, 1)
        
        reload_time_layout.addWidget(QLabel("开始时间:"), 0, 0)
        self.reload_start = QDateTimeEdit()
        self.reload_start.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.reload_start.setCalendarPopup(True)
        self.reload_start.setDateTime(QDateTime.currentDateTime().addDays(-7))
        reload_time_layout.addWidget(self.reload_start, 0, 1)
        
        reload_time_layout.addWidget(QLabel("结束时间:"), 1, 0)
        self.reload_end = QDateTimeEdit()
        self.reload_end.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.reload_end.setCalendarPopup(True)
        self.reload_end.setDateTime(QDateTime.currentDateTime())
        reload_time_layout.addWidget(self.reload_end, 1, 1)
        
        reload_layout.addLayout(reload_time_layout)
        
        # 窗口切分
        reload_split_layout = QHBoxLayout()
        reload_split_layout.addWidget(QLabel("窗口切分:"))
        self.reload_split_combo = QComboBox()
        self.reload_split_combo.addItem("不切分", "none")
        self.reload_split_combo.addItem("按天切分", "day")
        self.reload_split_combo.addItem("按周切分", "week")
        self.reload_split_combo.addItem("按月切分", "month")
        self.reload_split_combo.setToolTip("长时间窗口建议切分执行，避免单次请求过大")
        reload_split_layout.addWidget(self.reload_split_combo)
        reload_split_layout.addStretch()
        reload_layout.addLayout(reload_split_layout)
        
        # 任务选择
        reload_task_label = QLabel("选择 ODS 任务（按业务域）:")
        reload_layout.addWidget(reload_task_label)
        
        self.reload_task_selector = TaskSelectorWidget(
            show_dimensions=True,
            show_facts=True,
            default_select_facts=True,
            default_select_dimensions=False,
            compact=True,
            max_height=180,
        )
        reload_layout.addWidget(self.reload_task_selector)
        
        # 附加选项
        reload_options_layout = QHBoxLayout()
        self.reload_include_dwd = QCheckBox("ODS 完成后装载 DWD")
        self.reload_include_dwd.setChecked(True)
        reload_options_layout.addWidget(self.reload_include_dwd)
        
        self.reload_include_dimensions = QCheckBox("包含维度表")
        self.reload_include_dimensions.setChecked(False)
        self.reload_include_dimensions.setToolTip("勾选后会同时重刷维度类 ODS 任务")
        self.reload_include_dimensions.stateChanged.connect(self._on_reload_include_dimensions_changed)
        reload_options_layout.addWidget(self.reload_include_dimensions)
        reload_options_layout.addStretch()
        reload_layout.addLayout(reload_options_layout)
        
        # 操作按钮
        reload_btn_layout = QHBoxLayout()
        reload_btn_layout.addStretch()
        self.reload_run_btn = QPushButton("立即执行")
        self.reload_run_btn.setToolTip("添加到任务队列并执行")
        reload_btn_layout.addWidget(self.reload_run_btn)
        reload_layout.addLayout(reload_btn_layout)
        
        layout.addWidget(reload_group)
        
        # ====== 3. 数据完整性校验 ======
        integrity_group = QGroupBox("数据完整性校验")
        integrity_layout = QVBoxLayout(integrity_group)
        integrity_layout.setSpacing(8)
        
        # 说明
        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("指定时间窗口", "window")
        self.integrity_mode_combo.setToolTip("历史全量：按日期范围校验；指定窗口：精确到秒的时间窗口")
        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.setColumnStretch(1, 1)
        
        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.window_range_widget = QWidget()
        window_layout = QGridLayout(self.window_range_widget)
        window_layout.setContentsMargins(0, 0, 0, 0)
        window_layout.setColumnStretch(1, 1)
        
        window_layout.addWidget(QLabel("开始时间:"), 0, 0)
        self.integrity_window_start = QDateTimeEdit()
        self.integrity_window_start.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.integrity_window_start.setCalendarPopup(True)
        self.integrity_window_start.setDateTime(QDateTime.currentDateTime().addDays(-1))
        window_layout.addWidget(self.integrity_window_start, 0, 1)
        
        window_layout.addWidget(QLabel("结束时间:"), 1, 0)
        self.integrity_window_end = QDateTimeEdit()
        self.integrity_window_end.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        self.integrity_window_end.setCalendarPopup(True)
        self.integrity_window_end.setDateTime(QDateTime.currentDateTime())
        window_layout.addWidget(self.integrity_window_end, 1, 1)
        
        self.window_range_widget.setVisible(False)
        integrity_layout.addWidget(self.window_range_widget)
        
        # 校验选项分组
        int_options_group = QGroupBox("校验选项")
        int_options_layout = QVBoxLayout(int_options_group)
        int_options_layout.setSpacing(4)
        
        # 第一行选项
        int_opt_row1 = QHBoxLayout()
        self.integrity_include_dimensions = QCheckBox("包含维度表")
        self.integrity_include_dimensions.setToolTip("校验维度类 ODS 任务（如会员档案、商品档案等）")
        int_opt_row1.addWidget(self.integrity_include_dimensions)
        
        self.integrity_compare_content = QCheckBox("对比内容")
        self.integrity_compare_content.setChecked(True)
        self.integrity_compare_content.setToolTip("不仅对比记录数，还对比具体内容是否一致")
        int_opt_row1.addWidget(self.integrity_compare_content)
        int_opt_row1.addStretch()
        int_options_layout.addLayout(int_opt_row1)
        
        # 第二行选项
        int_opt_row2 = QHBoxLayout()
        self.integrity_auto_backfill = QCheckBox("自动补齐缺失数据")
        self.integrity_auto_backfill.setToolTip("发现缺失数据后，自动从 API 补充到 ODS")
        int_opt_row2.addWidget(self.integrity_auto_backfill)
        
        self.integrity_backfill_mismatch = QCheckBox("补齐不一致数据")
        self.integrity_backfill_mismatch.setToolTip("发现数据不一致时，也进行补齐（需先勾选自动补齐）")
        self.integrity_backfill_mismatch.setEnabled(False)
        int_opt_row2.addWidget(self.integrity_backfill_mismatch)
        int_opt_row2.addStretch()
        int_options_layout.addLayout(int_opt_row2)
        
        # 第三行选项
        int_opt_row3 = QHBoxLayout()
        self.integrity_recheck = QCheckBox("补齐后重新校验")
        self.integrity_recheck.setChecked(True)
        self.integrity_recheck.setToolTip("补齐数据后再执行一次校验，确认数据完整")
        self.integrity_recheck.setEnabled(False)
        int_opt_row3.addWidget(self.integrity_recheck)
        int_opt_row3.addStretch()
        int_options_layout.addLayout(int_opt_row3)
        
        integrity_layout.addWidget(int_options_group)
        
        # 指定任务
        int_task_layout = QHBoxLayout()
        int_task_layout.addWidget(QLabel("指定任务:"))
        self.integrity_ods_tasks = QLineEdit()
        self.integrity_ods_tasks.setPlaceholderText("留空=全部，或输入: ODS_PAYMENT,ODS_MEMBER")
        int_task_layout.addWidget(self.integrity_ods_tasks, 1)
        integrity_layout.addLayout(int_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.setProperty("secondary", True)
        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 _on_reload_include_dimensions_changed(self, state):
        """全量重刷的"包含维度表"选项变化"""
        include = state == Qt.Checked
        # 更新任务选择器的默认维度选择
        if include:
            # 选中维度类任务
            current_codes = self.reload_task_selector.get_selected_codes()
            dim_codes = get_dimension_ods_task_codes()
            all_codes = list(set(current_codes) | set(dim_codes))
            self.reload_task_selector.set_selected_codes(all_codes)
        else:
            # 取消选中维度类任务
            current_codes = self.reload_task_selector.get_selected_codes()
            dim_codes = set(get_dimension_ods_task_codes())
            fact_codes = [c for c in current_codes if c not in dim_codes]
            self.reload_task_selector.set_selected_codes(fact_codes)
    
    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.sync_run_btn.clicked.connect(self._run_daily_sync)
        self.sync_schedule_btn.clicked.connect(self._create_sync_schedule)
        
        # ====== 快捷操作 - 全量重刷 ======
        self.reload_run_btn.clicked.connect(self._run_full_reload)
        
        # ====== 快捷操作 - 数据校验 ======
        self.integrity_run_btn.clicked.connect(self._run_integrity_check)
        self.integrity_schedule_btn.clicked.connect(self._create_integrity_schedule)
        self.integrity_mode_combo.currentIndexChanged.connect(self._on_integrity_mode_changed)
        # 校验选项联动
        self.integrity_auto_backfill.stateChanged.connect(self._on_backfill_option_changed)
        
        # ====== 保存设置的信号连接 ======
        # 日常同步设置
        self.sync_hours.valueChanged.connect(self._save_sync_settings)
        self.sync_hours.valueChanged.connect(self._update_sync_time_preview)
        self.sync_overlap.valueChanged.connect(self._save_sync_settings)
        self.sync_overlap.valueChanged.connect(self._update_sync_time_preview)
        self.sync_include_dwd.stateChanged.connect(self._save_sync_settings)
        self.sync_auto_verify.stateChanged.connect(self._save_sync_settings)
        self.sync_task_selector.selection_changed.connect(self._save_sync_settings)
        
        # 全量重刷设置
        self.reload_start.dateTimeChanged.connect(self._save_reload_settings)
        self.reload_end.dateTimeChanged.connect(self._save_reload_settings)
        self.reload_split_combo.currentIndexChanged.connect(self._save_reload_settings)
        self.reload_include_dwd.stateChanged.connect(self._save_reload_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_include_dimensions.stateChanged.connect(self._save_integrity_settings)
        self.integrity_compare_content.stateChanged.connect(self._save_integrity_settings)
        self.integrity_auto_backfill.stateChanged.connect(self._save_integrity_settings)
        self.integrity_backfill_mismatch.stateChanged.connect(self._save_integrity_settings)
        self.integrity_recheck.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 _on_backfill_option_changed(self, state):
        """自动补齐选项变化时，联动其他选项"""
        enabled = state == Qt.Checked
        self.integrity_backfill_mismatch.setEnabled(enabled)
        self.integrity_recheck.setEnabled(enabled)
        if not enabled:
            self.integrity_backfill_mismatch.setChecked(False)
            self.integrity_recheck.setChecked(True)
    
    def refresh_tasks(self):
        """刷新任务列表"""
        self.task_list.clear()
        
        current_category = self.category_combo.currentData()
        
        # 从任务注册表动态获取任务列表
        for code, name, desc in _get_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.window_range_widget.setVisible(mode == "window")
    
    def _update_sync_time_preview(self):
        """更新日常同步的时间范围预览（精确到秒）"""
        now = datetime.now()
        hours = self.sync_hours.value()
        overlap = self.sync_overlap.value()
        start_time = now - timedelta(hours=hours, seconds=overlap)
        
        start_str = start_time.strftime("%Y-%m-%d %H:%M:%S")
        end_str = now.strftime("%Y-%m-%d %H:%M:%S")
        self.sync_time_preview.setText(f"时间窗口: {start_str} ~ {end_str}")
    
    # ==================== 日常增量同步 ====================
    
    def _run_daily_sync(self):
        """执行日常增量同步"""
        selected_tasks = self.sync_task_selector.get_selected_codes()
        
        if not selected_tasks:
            QMessageBox.warning(self, "提示", "请至少选择一个 ODS 任务")
            return
        
        hours = self.sync_hours.value()
        overlap = self.sync_overlap.value()
        include_dwd = self.sync_include_dwd.isChecked()
        auto_verify = self.sync_auto_verify.isChecked()
        
        now = datetime.now()
        start_time = now - timedelta(hours=hours, seconds=overlap)
        
        # 构建完整任务列表
        all_tasks = selected_tasks.copy()
        
        # 添加 DWD 装载
        if include_dwd:
            all_tasks.append("DWD_LOAD_FROM_ODS")
        
        # 添加数据校验
        if auto_verify:
            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"
            f"任务: {task_desc}\n"
            f"ODS 任务数: {len(selected_tasks)}\n"
            f"总任务数: {len(all_tasks)}\n\n"
            f"请切换到「任务管理」查看和执行"
        )
    
    def _create_sync_schedule(self):
        """创建日常同步调度任务"""
        selected_tasks = self.sync_task_selector.get_selected_codes()
        
        if not selected_tasks:
            QMessageBox.warning(self, "提示", "请至少选择一个 ODS 任务")
            return
        
        hours = self.sync_hours.value()
        include_dwd = self.sync_include_dwd.isChecked()
        
        # 构建任务列表
        all_tasks = selected_tasks.copy()
        if include_dwd:
            all_tasks.append("DWD_LOAD_FROM_ODS")
        
        task_config = {
            "pipeline_flow": "FULL",
            "lookback_hours": hours,
            "overlap_seconds": self.sync_overlap.value(),
        }
        
        # 发送信号创建调度任务
        self.create_schedule.emit(
            f"日常同步 ({hours}h)",
            all_tasks,
            task_config
        )
        self.log_message.emit(f"[GUI] 创建调度任务: 日常同步 ({hours}h)")
    
    # ==================== 全量重刷 ====================
    
    def _run_full_reload(self):
        """执行全量重刷"""
        selected_tasks = self.reload_task_selector.get_selected_codes()
        
        if not selected_tasks:
            QMessageBox.warning(self, "提示", "请至少选择一个 ODS 任务")
            return
        
        start_dt = self.reload_start.dateTime().toPython()
        end_dt = self.reload_end.dateTime().toPython()
        
        if start_dt >= end_dt:
            QMessageBox.warning(self, "提示", "开始时间必须早于结束时间")
            return
        
        include_dwd = self.reload_include_dwd.isChecked()
        split_unit = self.reload_split_combo.currentData()
        
        # 构建完整任务列表
        all_tasks = selected_tasks.copy()
        if include_dwd:
            all_tasks.append("DWD_LOAD_FROM_ODS")
        
        # 构建环境变量
        env_vars = {}
        if split_unit and split_unit != "none":
            env_vars["WINDOW_SPLIT_UNIT"] = split_unit
        
        config = TaskConfig(
            tasks=all_tasks,
            pipeline_flow="FULL",
            window_start=start_dt.strftime("%Y-%m-%d %H:%M:%S"),
            window_end=end_dt.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)
        
        # 计算时间跨度
        duration = end_dt - start_dt
        days = duration.days
        
        # 构建任务描述
        desc = f"全量重刷 ({days}天)"
        if include_dwd:
            desc += " + DWD"
        
        # 发送信号添加到队列
        self.add_to_queue.emit(config)
        self.log_message.emit(f"[GUI] 已添加到任务队列: {desc}")
        QMessageBox.information(
            self, "提示",
            f"已添加到任务队列\n\n"
            f"任务: {desc}\n"
            f"时间窗口: {start_dt.strftime('%Y-%m-%d %H:%M')} ~ {end_dt.strftime('%Y-%m-%d %H:%M')}\n"
            f"ODS 任务数: {len(selected_tasks)}\n"
            f"窗口切分: {split_unit if split_unit != 'none' else '不切分'}\n\n"
            f"请切换到「任务管理」查看和执行"
        )
    
    # ==================== 数据校验 ====================
    
    def _get_integrity_config(self) -> dict:
        """获取数据校验配置"""
        mode = self.integrity_mode_combo.currentData()
        
        config = {
            "pipeline_flow": "FULL",
            "integrity_mode": mode,
            "integrity_include_dimensions": self.integrity_include_dimensions.isChecked(),
            "integrity_compare_content": self.integrity_compare_content.isChecked(),
            "integrity_auto_backfill": self.integrity_auto_backfill.isChecked(),
            "integrity_backfill_mismatch": self.integrity_backfill_mismatch.isChecked(),
            "integrity_recheck": self.integrity_recheck.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["window_start"] = self.integrity_window_start.dateTime().toPython().strftime("%Y-%m-%d %H:%M:%S")
            config["window_end"] = self.integrity_window_end.dateTime().toPython().strftime("%Y-%m-%d %H:%M:%S")
        
        ods_tasks = self.integrity_ods_tasks.text().strip()
        if ods_tasks:
            config["integrity_ods_task_codes"] = ods_tasks
        
        return config
    
    def _run_integrity_check(self):
        """执行数据完整性校验"""
        mode = self.integrity_mode_combo.currentData()
        int_config = self._get_integrity_config()
        
        # 构建环境变量
        env_vars = {
            "INTEGRITY_MODE": int_config.get("integrity_mode", "history"),
            "INTEGRITY_INCLUDE_DIMENSIONS": "1" if int_config.get("integrity_include_dimensions") else "0",
            "INTEGRITY_COMPARE_CONTENT": "1" if int_config.get("integrity_compare_content") else "0",
            "INTEGRITY_AUTO_BACKFILL": "1" if int_config.get("integrity_auto_backfill") else "0",
            "INTEGRITY_BACKFILL_MISMATCH": "1" if int_config.get("integrity_backfill_mismatch") else "0",
            "INTEGRITY_RECHECK_AFTER_BACKFILL": "1" if int_config.get("integrity_recheck") else "0",
        }
        
        window_start = None
        window_end = None
        
        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:
            window_start = int_config.get("window_start")
            window_end = int_config.get("window_end")
            desc = f"数据校验 ({window_start} ~ {window_end})"
        
        if int_config.get("integrity_ods_task_codes"):
            env_vars["INTEGRITY_ODS_TASK_CODES"] = int_config.get("integrity_ods_task_codes")
        
        # 构建描述
        options = []
        if int_config.get("integrity_include_dimensions"):
            options.append("含维度")
        if int_config.get("integrity_auto_backfill"):
            options.append("自动补齐")
        if options:
            desc += " [" + ", ".join(options) + "]"
        
        config = TaskConfig(
            tasks=["DATA_INTEGRITY_CHECK"],
            pipeline_flow="FULL",
            window_start=window_start,
            window_end=window_end,
            env_vars=env_vars,
        )
        
        # 更新预览
        cmd_str = self.cli_builder.build_command_string(config)
        env_preview = "\n".join(f"# {k}={v}" for k, v in env_vars.items())
        self.cli_preview.setPlainText(f"{cmd_str}\n\n# 环境变量:\n{env_preview}")
        
        # 发送信号添加到队列
        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:
            desc = "数据校验 (窗口模式)"
        
        # 发送信号创建调度任务
        self.create_schedule.emit(
            desc,
            ["DATA_INTEGRITY_CHECK"],
            task_config
        )
        self.log_message.emit(f"[GUI] 创建调度任务: {desc}")
    
    # ==================== 设置持久化 ====================
    
    def _load_settings(self):
        """从持久化存储加载设置"""
        try:
            # 加载日常同步设置
            if hasattr(app_settings, 'sync_hours'):
                self.sync_hours.setValue(app_settings.sync_hours)
            if hasattr(app_settings, 'sync_overlap'):
                self.sync_overlap.setValue(app_settings.sync_overlap)
            if hasattr(app_settings, 'sync_include_dwd'):
                self.sync_include_dwd.setChecked(app_settings.sync_include_dwd)
            if hasattr(app_settings, 'sync_auto_verify'):
                self.sync_auto_verify.setChecked(app_settings.sync_auto_verify)
            
            # 恢复日常同步任务选择
            if hasattr(app_settings, 'sync_selected_tasks'):
                saved_tasks = app_settings.sync_selected_tasks
                if saved_tasks:
                    self.sync_task_selector.set_selected_codes(saved_tasks)
            
            # 加载数据校验设置
            mode = getattr(app_settings, 'integrity_mode', 'history')
            mode_index = 0 if mode == "history" else 1
            self.integrity_mode_combo.setCurrentIndex(mode_index)
            
            # 历史日期
            if hasattr(app_settings, 'integrity_history_start') and 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 hasattr(app_settings, 'integrity_history_end') and 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
            
            if hasattr(app_settings, 'integrity_include_dimensions'):
                self.integrity_include_dimensions.setChecked(app_settings.integrity_include_dimensions)
            if hasattr(app_settings, 'integrity_compare_content'):
                self.integrity_compare_content.setChecked(app_settings.integrity_compare_content)
            if hasattr(app_settings, 'integrity_auto_backfill'):
                self.integrity_auto_backfill.setChecked(app_settings.integrity_auto_backfill)
            if hasattr(app_settings, 'integrity_ods_tasks'):
                self.integrity_ods_tasks.setText(app_settings.integrity_ods_tasks)
            
            # 加载高级设置
            pipeline_flow = getattr(app_settings, 'advanced_pipeline_flow', 'FULL')
            flow_map = {"FULL": 0, "FETCH_ONLY": 1, "INGEST_ONLY": 2}
            self.pipeline_flow_combo.setCurrentIndex(flow_map.get(pipeline_flow, 0))
            
            if hasattr(app_settings, 'advanced_dry_run'):
                self.dry_run_check.setChecked(app_settings.advanced_dry_run)
            if hasattr(app_settings, 'advanced_window_start'):
                self.window_start_edit.setText(app_settings.advanced_window_start)
            if hasattr(app_settings, 'advanced_window_end'):
                self.window_end_edit.setText(app_settings.advanced_window_end)
            if hasattr(app_settings, 'advanced_ingest_source'):
                self.ingest_source_edit.setText(app_settings.advanced_ingest_source)
            
            # 加载窗口切分设置
            split_map = {"none": 0, "day": 1, "week": 2, "month": 3}
            if hasattr(app_settings, 'advanced_window_split'):
                self.window_split_combo.setCurrentIndex(split_map.get(app_settings.advanced_window_split, 0))
            if hasattr(app_settings, 'advanced_window_compensation'):
                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_sync_settings(self):
        """保存日常同步设置"""
        try:
            app_settings.sync_hours = self.sync_hours.value()
            app_settings.sync_overlap = self.sync_overlap.value()
            app_settings.sync_include_dwd = self.sync_include_dwd.isChecked()
            app_settings.sync_auto_verify = self.sync_auto_verify.isChecked()
            app_settings.sync_selected_tasks = self.sync_task_selector.get_selected_codes()
        except Exception as e:
            print(f"保存日常同步设置失败: {e}")
    
    def _save_reload_settings(self):
        """保存全量重刷设置"""
        try:
            app_settings.reload_split = self.reload_split_combo.currentData() or "none"
            app_settings.reload_include_dwd = self.reload_include_dwd.isChecked()
        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_include_dimensions = self.integrity_include_dimensions.isChecked()
            app_settings.integrity_compare_content = self.integrity_compare_content.isChecked()
            app_settings.integrity_auto_backfill = self.integrity_auto_backfill.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}")
