#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
对话框工具模块
-------------
提供各种弹窗和对话框显示功能
"""

import os
import json
import tkinter as tk
from tkinter import messagebox, ttk, simpledialog
from datetime import datetime

from app.core.utils.cloud_sync import GiteaSync
from app.config.settings import ConfigManager

def create_custom_dialog(title="提示", message="", result_file=None, time_info=None, 
                        count_info=None, amount_info=None, additional_info=None):
    """
    创建自定义结果对话框
    
    Args:
        title: 对话框标题
        message: 主要消息
        result_file: 结果文件路径（如果有）
        time_info: 时间信息（如：订单时间）
        count_info: 数量信息（如：处理条目数）
        amount_info: 金额信息（如：总金额）
        additional_info: 其他附加信息（字典格式）
        
    Returns:
        dialog: 对话框对象
    """
    # 创建对话框
    dialog = tk.Toplevel()
    dialog.title(title)
    dialog.geometry("450x320")
    dialog.resizable(False, False)
    
    # 使弹窗居中显示
    center_window(dialog)
    
    # 添加标题
    tk.Label(dialog, text=message, font=("Arial", 16, "bold")).pack(pady=10)
    
    # 创建内容框架
    result_frame = tk.Frame(dialog)
    result_frame.pack(pady=10, fill=tk.BOTH, expand=True)
    
    # 添加时间、数量、金额等信息
    if time_info:
        tk.Label(result_frame, text=f"时间信息: {time_info}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
    
    if count_info:
        tk.Label(result_frame, text=f"处理数量: {count_info}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
    
    if amount_info:
        tk.Label(result_frame, text=f"金额信息: {amount_info}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
    
    # 添加其他附加信息
    if additional_info and isinstance(additional_info, dict):
        for key, value in additional_info.items():
            tk.Label(result_frame, text=f"{key}: {value}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
    
    # 如果有结果文件，显示文件信息
    if result_file and os.path.exists(result_file):
        tk.Label(result_frame, text=f"输出文件: {os.path.basename(result_file)}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
        
        # 成功提示
        tk.Label(result_frame, text="处理已成功完成！", font=("Arial", 12, "bold"), fg="#28a745").pack(pady=10)
        
        # 文件信息框
        file_frame = tk.Frame(result_frame, relief=tk.GROOVE, borderwidth=1)
        file_frame.pack(fill=tk.X, padx=15, pady=5)
        
        tk.Label(file_frame, text="文件信息", font=("Arial", 10, "bold")).pack(anchor=tk.W, padx=10, pady=5)
        
        # 获取文件大小和时间
        try:
            file_size = os.path.getsize(result_file)
            file_time = datetime.fromtimestamp(os.path.getmtime(result_file))
            
            from app.core.utils.file_utils import format_file_size
            size_text = format_file_size(file_size)
            
            tk.Label(file_frame, text=f"文件大小: {size_text}", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2)
            tk.Label(file_frame, text=f"创建时间: {file_time.strftime('%Y-%m-%d %H:%M:%S')}", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2)
        except Exception:
            tk.Label(file_frame, text="无法获取文件信息", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2)
        
        # 添加按钮
        button_frame = tk.Frame(dialog)
        button_frame.pack(pady=10)
        
        tk.Button(button_frame, text="打开文件", command=lambda: os.startfile(result_file)).pack(side=tk.LEFT, padx=5)
        tk.Button(button_frame, text="打开所在文件夹", command=lambda: os.startfile(os.path.dirname(result_file))).pack(side=tk.LEFT, padx=5)
        tk.Button(button_frame, text="关闭", command=dialog.destroy).pack(side=tk.LEFT, padx=5)
    else:
        # 如果没有结果文件或文件不存在
        if result_file:
            tk.Label(result_frame, text="未找到输出文件", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
            tk.Label(result_frame, text="请检查输出目录", font=("Arial", 12, "bold"), fg="#dc3545").pack(pady=10)
        
        # 添加按钮
        button_frame = tk.Frame(dialog)
        button_frame.pack(pady=10)
        
        tk.Button(button_frame, text="打开输出目录", command=lambda: os.startfile(ConfigManager().get_path('Paths', 'output_folder', fallback='data/output', create=True))).pack(side=tk.LEFT, padx=5)
        tk.Button(button_frame, text="关闭", command=dialog.destroy).pack(side=tk.LEFT, padx=5)
    
    # 确保窗口显示在最前
    dialog.lift()
    dialog.attributes('-topmost', True)
    dialog.after_idle(lambda: dialog.attributes('-topmost', False))
    
    return dialog

def show_custom_dialog(*args, **kwargs):
    """
    显示自定义对话框
    
    参数与create_custom_dialog相同
    
    Returns:
        dialog: 对话框对象
    """
    return create_custom_dialog(*args, **kwargs)

def center_window(window):
    """使窗口居中显示"""
    window.update_idletasks()
    width = window.winfo_width()
    height = window.winfo_height()
    x = (window.winfo_screenwidth() // 2) - (width // 2)
    y = (window.winfo_screenheight() // 2) - (height // 2)
    window.geometry('{}x{}+{}+{}'.format(width, height, x, y))

def create_barcode_mapping_dialog(parent=None, on_save=None, current_mappings=None):
    """
    创建条码映射编辑弹窗
    
    Args:
        parent: 父窗口
        on_save: 保存回调函数，接收修改后的映射数据
        current_mappings: 当前的映射数据
        
    Returns:
        dialog: 对话框对象
    """
    dialog = tk.Toplevel(parent)
    dialog.title("条码映射编辑")
    dialog.geometry("600x500")
    dialog.resizable(True, True)
    
    # 使弹窗居中显示
    center_window(dialog)
    
    # 创建主框架
    main_frame = tk.Frame(dialog)
    main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
    
    # 创建选项卡控件
    tab_control = ttk.Notebook(main_frame)
    
    # 创建两个选项卡页面
    tab1 = tk.Frame(tab_control)
    tab2 = tk.Frame(tab_control)
    
    tab_control.add(tab1, text="条码映射")
    tab_control.add(tab2, text="特殊处理")
    tab_control.pack(expand=True, fill=tk.BOTH)
    
    # ========= 条码映射选项卡 =========
    # 顶部输入区域
    input_frame = tk.Frame(tab1)
    input_frame.pack(fill=tk.X, padx=5, pady=5)
    
    tk.Label(input_frame, text="源条码:").grid(row=0, column=0, padx=5, pady=5)
    source_entry = tk.Entry(input_frame, width=20)
    source_entry.grid(row=0, column=1, padx=5, pady=5)
    
    tk.Label(input_frame, text="目标条码:").grid(row=0, column=2, padx=5, pady=5)
    target_entry = tk.Entry(input_frame, width=20)
    target_entry.grid(row=0, column=3, padx=5, pady=5)
    
    # 存储映射列表的变量
    mapping_list = []
    
    # 映射列表显示区域
    list_frame = tk.Frame(tab1)
    list_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
    
    columns = ("源条码", "目标条码")
    mapping_tree = ttk.Treeview(list_frame, columns=columns, show="headings", selectmode="browse")
    
    for col in columns:
        mapping_tree.heading(col, text=col)
        mapping_tree.column(col, width=100)
    
    mapping_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
    
    # 添加滚动条
    scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=mapping_tree.yview)
    scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
    mapping_tree.configure(yscrollcommand=scrollbar.set)
    
    # ========= 特殊处理选项卡 =========
    # 顶部输入区域
    special_input_frame = tk.Frame(tab2)
    special_input_frame.pack(fill=tk.X, padx=5, pady=5)
    
    tk.Label(special_input_frame, text="条码:").grid(row=0, column=0, padx=5, pady=5)
    special_barcode_entry = tk.Entry(special_input_frame, width=20)
    special_barcode_entry.grid(row=0, column=1, padx=5, pady=5)
    
    tk.Label(special_input_frame, text="乘数:").grid(row=1, column=0, padx=5, pady=5)
    multiplier_entry = tk.Entry(special_input_frame, width=10)
    multiplier_entry.grid(row=1, column=1, padx=5, pady=5)
    
    tk.Label(special_input_frame, text="目标单位:").grid(row=1, column=2, padx=5, pady=5)
    unit_entry = tk.Entry(special_input_frame, width=10)
    unit_entry.grid(row=1, column=3, padx=5, pady=5)
    
    tk.Label(special_input_frame, text="固定单价:").grid(row=2, column=0, padx=5, pady=5)
    price_entry = tk.Entry(special_input_frame, width=10)
    price_entry.grid(row=2, column=1, padx=5, pady=5)
    
    tk.Label(special_input_frame, text="规格:").grid(row=2, column=2, padx=5, pady=5)
    spec_entry = tk.Entry(special_input_frame, width=10)
    spec_entry.grid(row=2, column=3, padx=5, pady=5)
    
    tk.Label(special_input_frame, text="描述:").grid(row=3, column=0, padx=5, pady=5)
    desc_entry = tk.Entry(special_input_frame, width=40)
    desc_entry.grid(row=3, column=1, columnspan=3, padx=5, pady=5)
    
    # 特殊处理列表显示区域
    special_list_frame = tk.Frame(tab2)
    special_list_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
    
    special_columns = ("条码", "乘数", "目标单位", "固定单价", "规格", "描述")
    special_tree = ttk.Treeview(special_list_frame, columns=special_columns, show="headings", selectmode="browse")
    
    for col in special_columns:
        special_tree.heading(col, text=col)
        special_tree.column(col, width=80)
    
    special_tree.column("描述", width=200)
    special_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
    
    # 添加滚动条
    special_scrollbar = ttk.Scrollbar(special_list_frame, orient=tk.VERTICAL, command=special_tree.yview)
    special_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
    special_tree.configure(yscrollcommand=special_scrollbar.set)
    
    # 存储特殊处理列表的变量
    special_list = []
    
    # 按钮区域
    def add_mapping():
        source = source_entry.get().strip()
        target = target_entry.get().strip()
        
        if not source or not target:
            messagebox.showwarning("输入错误", "源条码和目标条码不能为空")
            return
            
        # 检查是否已存在
        for item in mapping_list:
            if item[0] == source:
                messagebox.showwarning("重复条码", f"条码 {source} 已存在映射")
                return
        
        # 添加到列表
        mapping_list.append((source, target))
        mapping_tree.insert("", tk.END, values=(source, target))
        
        # 清空输入框
        source_entry.delete(0, tk.END)
        target_entry.delete(0, tk.END)
    
    def remove_mapping():
        selected = mapping_tree.selection()
        if not selected:
            messagebox.showwarning("未选择", "请先选择要删除的条目")
            return
        
        # 获取选中项的索引
        item = mapping_tree.item(selected[0])
        source = item['values'][0]
        
        # 从列表中移除
        for i, (s, _) in enumerate(mapping_list):
            if s == source:
                mapping_list.pop(i)
                break
        
        # 从树中移除
        mapping_tree.delete(selected[0])
    
    def add_special():
        barcode = special_barcode_entry.get().strip()
        multiplier = multiplier_entry.get().strip()
        unit = unit_entry.get().strip()
        price = price_entry.get().strip()
        spec = spec_entry.get().strip()
        desc = desc_entry.get().strip()
        
        if not barcode:
            messagebox.showwarning("输入错误", "条码不能为空")
            return
            
        # 检查是否已存在
        for item in special_list:
            if item[0] == barcode:
                messagebox.showwarning("重复条码", f"条码 {barcode} 已存在特殊处理")
                return
        
        # 添加到列表
        special_list.append((barcode, multiplier, unit, price, spec, desc))
        special_tree.insert("", tk.END, values=(barcode, multiplier, unit, price, spec, desc))
        
        # 清空输入框
        special_barcode_entry.delete(0, tk.END)
        multiplier_entry.delete(0, tk.END)
        unit_entry.delete(0, tk.END)
        price_entry.delete(0, tk.END)
        spec_entry.delete(0, tk.END)
        desc_entry.delete(0, tk.END)
    
    def remove_special():
        selected = special_tree.selection()
        if not selected:
            messagebox.showwarning("未选择", "请先选择要删除的条目")
            return
        
        # 获取选中项的索引
        item = special_tree.item(selected[0])
        barcode = item['values'][0]
        
        # 从列表中移除
        for i, (b, _, _, _, _, _) in enumerate(special_list):
            if b == barcode:
                special_list.pop(i)
                break
        
        # 从树中移除
        special_tree.delete(selected[0])
    
    # 条码映射按钮
    btn_frame = tk.Frame(tab1)
    btn_frame.pack(fill=tk.X, padx=5, pady=5)
    
    add_btn = tk.Button(btn_frame, text="添加映射", command=add_mapping)
    add_btn.pack(side=tk.LEFT, padx=5)
    
    remove_btn = tk.Button(btn_frame, text="删除映射", command=remove_mapping)
    remove_btn.pack(side=tk.LEFT, padx=5)
    
    # 特殊处理按钮
    special_btn_frame = tk.Frame(tab2)
    special_btn_frame.pack(fill=tk.X, padx=5, pady=5)
    
    add_special_btn = tk.Button(special_btn_frame, text="添加特殊处理", command=add_special)
    add_special_btn.pack(side=tk.LEFT, padx=5)
    
    remove_special_btn = tk.Button(special_btn_frame, text="删除特殊处理", command=remove_special)
    remove_special_btn.pack(side=tk.LEFT, padx=5)
    
    # 添加映射到特殊处理的功能
    def add_mapping_to_special():
        selected = special_tree.selection()
        if not selected:
            messagebox.showwarning("未选择", "请先选择要添加映射的特殊处理条目")
            return
        
        # 获取选中项
        item = special_tree.item(selected[0])
        barcode = item['values'][0]
        
        # 弹出对话框输入映射目标
        target_barcode = tk.simpledialog.askstring("添加映射", f"为条码 {barcode} 添加映射目标条码:")
        if not target_barcode:
            return
            
        # 更新特殊处理列表中的项
        for i, (b, mult, unit, price, spec, desc) in enumerate(special_list):
            if b == barcode:
                # 如果描述中已有映射信息，更新它
                if "映射到:" in desc:
                    desc = desc.split("映射到:")[0].strip()
                
                # 添加映射信息到描述
                new_desc = f"{desc} 映射到: {target_barcode}"
                special_list[i] = (b, mult, unit, price, spec, new_desc)
                
                # 更新显示
                special_tree.item(selected[0], values=(b, mult, unit, price, spec, new_desc))
                
                # 标记该条码有映射
                special_tree.item(selected[0], tags=("mapped",))
                special_tree.tag_configure("mapped", foreground="blue")
                
                break
                
    map_special_btn = tk.Button(special_btn_frame, text="添加条码映射", command=add_mapping_to_special)
    map_special_btn.pack(side=tk.LEFT, padx=5)
    
    # 底部按钮区域
    bottom_frame = tk.Frame(dialog)
    bottom_frame.pack(fill=tk.X, padx=10, pady=10)
    
    def save_mappings():
        # 构建保存数据
        mappings = {}
        
        # 添加条码映射
        for source, target in mapping_list:
            mappings[source] = {
                'map_to': target,
                'description': f'条码映射：{source} -> {target}'
            }
        
        # 添加特殊处理
        for barcode, multiplier, unit, price, spec, desc in special_list:
            # 检查该条码是否已存在
            if barcode not in mappings:
                mappings[barcode] = {}
            
            if multiplier:
                try:
                    # 安全地转换multiplier为数字
                    if isinstance(multiplier, str):
                        if '.' in multiplier:
                            mappings[barcode]['multiplier'] = float(multiplier)
                        else:
                            mappings[barcode]['multiplier'] = int(multiplier)
                    else:
                        # 已经是数字类型
                        mappings[barcode]['multiplier'] = multiplier
                except ValueError:
                    # 如果转换失败，保持原始字符串
                    mappings[barcode]['multiplier'] = multiplier
            
            if unit:
                mappings[barcode]['target_unit'] = unit
            
            if price:
                try:
                    # 安全地转换price为浮点数
                    mappings[barcode]['fixed_price'] = float(price)
                except ValueError:
                    # 如果转换失败，保持原始字符串
                    mappings[barcode]['fixed_price'] = price
            
            if spec:
                mappings[barcode]['specification'] = spec
            
            # 检查描述中是否包含映射信息
            if desc and "映射到:" in desc:
                parts = desc.split("映射到:")
                base_desc = parts[0].strip()
                target_barcode = parts[1].strip()
                
                # 设置基本描述
                if base_desc:
                    mappings[barcode]['description'] = base_desc
                
                # 设置映射目标
                mappings[barcode]['map_to'] = target_barcode
            elif desc:
                mappings[barcode]['description'] = desc
        
        # 调用保存回调
        if on_save:
            on_save(mappings)
        
        messagebox.showinfo("保存成功", f"已保存{len(mapping_list)}个条码映射和{len(special_list)}个特殊处理规则")
        dialog.destroy()
    
    def cancel():
        dialog.destroy()
    
    save_btn = tk.Button(bottom_frame, text="保存", command=save_mappings)
    save_btn.pack(side=tk.RIGHT, padx=5)

    cancel_btn = tk.Button(bottom_frame, text="取消", command=cancel)
    cancel_btn.pack(side=tk.RIGHT, padx=5)

    # ---- 云端同步按钮 ----
    def _build_current_mappings():
        """从弹窗当前数据构建 mappings dict（与 save_mappings 逻辑相同）"""
        mappings = {}
        for source, target in mapping_list:
            mappings[source] = {
                'map_to': target,
                'description': f'条码映射：{source} -> {target}'
            }
        for barcode, multiplier, unit, price, spec, desc in special_list:
            if barcode not in mappings:
                mappings[barcode] = {}
            if multiplier:
                try:
                    if isinstance(multiplier, str):
                        mappings[barcode]['multiplier'] = float(multiplier) if '.' in multiplier else int(multiplier)
                    else:
                        mappings[barcode]['multiplier'] = multiplier
                except ValueError:
                    mappings[barcode]['multiplier'] = multiplier
            if unit:
                mappings[barcode]['target_unit'] = unit
            if price:
                try:
                    mappings[barcode]['fixed_price'] = float(price)
                except ValueError:
                    mappings[barcode]['fixed_price'] = price
            if spec:
                mappings[barcode]['specification'] = spec
            if desc and "映射到:" in desc:
                parts = desc.split("映射到:")
                base_desc = parts[0].strip()
                target_barcode = parts[1].strip()
                if base_desc:
                    mappings[barcode]['description'] = base_desc
                mappings[barcode]['map_to'] = target_barcode
            elif desc:
                mappings[barcode]['description'] = desc
        return mappings

    def _get_sync():
        """获取 GiteaSync 实例，配置不完整时提示用户"""
        sync = GiteaSync.from_config(ConfigManager())
        if sync is None:
            messagebox.showwarning("云端同步", "请先在「系统设置」中配置 Gitea 云端同步参数（token）")
        return sync

    def _refresh_trees(new_mappings):
        """用新数据刷新两个 Treeview"""
        # 清空
        for item in mapping_tree.get_children():
            mapping_tree.delete(item)
        mapping_list.clear()
        for item in special_tree.get_children():
            special_tree.delete(item)
        special_list.clear()
        # 重新填充
        if new_mappings:
            for barcode, data in new_mappings.items():
                if 'map_to' in data and 'multiplier' not in data:
                    mapping_list.append((barcode, data['map_to']))
                    mapping_tree.insert('', 'end', values=(barcode, data['map_to']))
                else:
                    mult = data.get('multiplier', '')
                    unit = data.get('target_unit', '')
                    price = data.get('fixed_price', '')
                    spec = data.get('specification', '')
                    desc = data.get('description', '')
                    if 'map_to' in data:
                        desc = f"{desc} 映射到: {data['map_to']}" if desc else f"映射到: {data['map_to']}"
                    special_list.append((barcode, mult, unit, price, spec, desc))
                    tags = ("mapped",) if 'map_to' in data else ()
                    special_tree.insert('', 'end', values=(barcode, mult, unit, price, spec, desc), tags=tags)
            if any('map_to' in d for d in new_mappings.values()):
                special_tree.tag_configure("mapped", foreground="blue")

    def push_to_cloud():
        sync = _get_sync()
        if not sync:
            return
        mappings = _build_current_mappings()
        if not mappings:
            messagebox.showwarning("同步到云端", "当前没有映射数据可同步")
            return
        # 先获取当前 sha（如果文件已存在）
        sha = None
        existing = sync.pull_file("barcode_mappings.json")
        if existing:
            sha = existing[1]
        new_sha = sync.push_json(
            "barcode_mappings.json",
            mappings,
            f"同步条码映射 ({len(mappings)} 条)",
            sha=sha,
        )
        if new_sha:
            messagebox.showinfo("同步成功", f"已推送 {len(mappings)} 条映射到云端")
        else:
            messagebox.showerror("同步失败", "推送到云端失败，请检查网络和 Gitea 配置")

    def pull_from_cloud():
        sync = _get_sync()
        if not sync:
            return
        result = sync.pull_json("barcode_mappings.json")
        if result is None:
            messagebox.showwarning("拉取失败", "云端没有找到条码映射文件，或网络错误")
            return
        data, sha = result
        if not isinstance(data, dict) or len(data) == 0:
            messagebox.showwarning("拉取失败", "云端数据格式异常")
            return
        # 同时保存到本地
        from app.core.excel.converter import UnitConverter
        uc = UnitConverter()
        uc.update_barcode_mappings(data)
        # 刷新弹窗
        _refresh_trees(data)
        messagebox.showinfo("拉取成功", f"已从云端拉取 {len(data)} 条映射，本地已同步更新")

    sync_frame = tk.Frame(bottom_frame)
    sync_frame.pack(side=tk.LEFT, padx=5)

    push_btn = tk.Button(sync_frame, text="同步到云端", command=push_to_cloud, fg="white", bg="#4a90d9")
    push_btn.pack(side=tk.LEFT, padx=3)

    pull_btn = tk.Button(sync_frame, text="从云端拉取", command=pull_from_cloud, fg="white", bg="#5cb85c")
    pull_btn.pack(side=tk.LEFT, padx=3)
    
    # 导入当前映射数据
    if current_mappings:
        for barcode, data in current_mappings.items():
            if 'map_to' in data:
                # 这是条码映射
                mapping_list.append((barcode, data['map_to']))
                mapping_tree.insert("", tk.END, values=(barcode, data['map_to']))
            else:
                # 这是特殊处理
                multiplier = data.get('multiplier', '')
                unit = data.get('target_unit', '')
                price = data.get('fixed_price', '')
                spec = data.get('specification', '')
                desc = data.get('description', '')
                
                special_list.append((barcode, multiplier, unit, price, spec, desc))
                special_tree.insert("", tk.END, values=(barcode, multiplier, unit, price, spec, desc))
    
    # 确保窗口显示在最前
    dialog.transient(parent)
    dialog.grab_set()
    
    return dialog

def show_barcode_mapping_dialog(*args, **kwargs):
    """
    显示条码映射编辑弹窗
    
    参数与create_barcode_mapping_dialog相同
    
    Returns:
        dialog: 对话框对象
    """
    # 确保已导入ttk
    import tkinter.ttk as ttk
    return create_barcode_mapping_dialog(*args, **kwargs)

def show_config_dialog(parent, config_manager, on_save=None):
    """显示配置设置对话框"""
    dialog = tk.Toplevel(parent)
    dialog.title("系统配置")
    dialog.geometry("600x500")
    dialog.resizable(False, False)
    
    # 使窗口居中
    dialog.update_idletasks()
    width = dialog.winfo_width()
    height = dialog.winfo_height()
    x = (dialog.winfo_screenwidth() // 2) - (width // 2)
    y = (dialog.winfo_screenheight() // 2) - (height // 2)
    dialog.geometry('{}x{}+{}+{}'.format(width, height, x, y))
    
    # 创建主框架
    main_frame = ttk.Frame(dialog, padding="10")
    main_frame.pack(fill=tk.BOTH, expand=True)
    
    # 创建选项卡
    notebook = ttk.Notebook(main_frame)
    notebook.pack(fill=tk.BOTH, expand=True, pady=5)
    
    # 创建各个配置页面的框架
    api_frame = ttk.Frame(notebook, padding="10")
    paths_frame = ttk.Frame(notebook, padding="10")
    performance_frame = ttk.Frame(notebook, padding="10")
    file_frame = ttk.Frame(notebook, padding="10")
    
    # 添加选项卡
    notebook.add(api_frame, text="API设置")
    notebook.add(paths_frame, text="路径设置")
    notebook.add(performance_frame, text="性能设置")
    notebook.add(file_frame, text="文件设置")
    
    # 存储所有输入框的引用
    entries = {}
    
    # API设置
    ttk.Label(api_frame, text="百度OCR API设置", font=("Arial", 12, "bold")).pack(anchor=tk.W, pady=5)
    
    # API Key
    ttk.Label(api_frame, text="API Key:").pack(anchor=tk.W, pady=2)
    api_key_entry = ttk.Entry(api_frame, width=50)
    api_key_entry.pack(fill=tk.X, pady=2)
    api_key_entry.insert(0, config_manager.get('API', 'api_key', ''))
    entries[('API', 'api_key')] = api_key_entry
    
    # Secret Key
    ttk.Label(api_frame, text="Secret Key:").pack(anchor=tk.W, pady=2)
    secret_key_entry = ttk.Entry(api_frame, width=50)
    secret_key_entry.pack(fill=tk.X, pady=2)
    secret_key_entry.insert(0, config_manager.get('API', 'secret_key', ''))
    entries[('API', 'secret_key')] = secret_key_entry
    
    # 超时设置
    ttk.Label(api_frame, text="超时时间(秒):").pack(anchor=tk.W, pady=2)
    timeout_entry = ttk.Entry(api_frame, width=10)
    timeout_entry.pack(anchor=tk.W, pady=2)
    timeout_entry.insert(0, config_manager.get('API', 'timeout', '30'))
    entries[('API', 'timeout')] = timeout_entry
    
    # 路径设置
    ttk.Label(paths_frame, text="系统路径设置", font=("Arial", 12, "bold")).pack(anchor=tk.W, pady=5)
    
    # 输入目录
    ttk.Label(paths_frame, text="输入目录:").pack(anchor=tk.W, pady=2)
    input_dir_entry = ttk.Entry(paths_frame, width=50)
    input_dir_entry.pack(fill=tk.X, pady=2)
    input_dir_entry.insert(0, config_manager.get('Paths', 'input_folder', 'data/input'))
    entries[('Paths', 'input_folder')] = input_dir_entry
    
    # 输出目录
    ttk.Label(paths_frame, text="输出目录:").pack(anchor=tk.W, pady=2)
    output_dir_entry = ttk.Entry(paths_frame, width=50)
    output_dir_entry.pack(fill=tk.X, pady=2)
    output_dir_entry.insert(0, config_manager.get('Paths', 'output_folder', 'data/output'))
    entries[('Paths', 'output_folder')] = output_dir_entry
    
    # 性能设置
    ttk.Label(performance_frame, text="性能设置", font=("Arial", 12, "bold")).pack(anchor=tk.W, pady=5)
    
    # 最大工作线程数
    ttk.Label(performance_frame, text="最大工作线程数:").pack(anchor=tk.W, pady=2)
    max_workers_entry = ttk.Entry(performance_frame, width=10)
    max_workers_entry.pack(anchor=tk.W, pady=2)
    max_workers_entry.insert(0, config_manager.get('Performance', 'max_workers', '4'))
    entries[('Performance', 'max_workers')] = max_workers_entry
    
    # 批处理大小
    ttk.Label(performance_frame, text="批处理大小:").pack(anchor=tk.W, pady=2)
    batch_size_entry = ttk.Entry(performance_frame, width=10)
    batch_size_entry.pack(anchor=tk.W, pady=2)
    batch_size_entry.insert(0, config_manager.get('Performance', 'batch_size', '5'))
    entries[('Performance', 'batch_size')] = batch_size_entry
    
    # 文件设置
    ttk.Label(file_frame, text="文件设置", font=("Arial", 12, "bold")).pack(anchor=tk.W, pady=5)
    
    # 允许的文件扩展名
    ttk.Label(file_frame, text="允许的文件扩展名:").pack(anchor=tk.W, pady=2)
    extensions_entry = ttk.Entry(file_frame, width=50)
    extensions_entry.pack(fill=tk.X, pady=2)
    extensions_entry.insert(0, config_manager.get('File', 'allowed_extensions', '.jpg,.jpeg,.png,.bmp'))
    entries[('File', 'allowed_extensions')] = extensions_entry
    
    # 最大文件大小
    ttk.Label(file_frame, text="最大文件大小(MB):").pack(anchor=tk.W, pady=2)
    max_size_entry = ttk.Entry(file_frame, width=10)
    max_size_entry.pack(anchor=tk.W, pady=2)
    max_size_entry.insert(0, config_manager.get('File', 'max_file_size_mb', '4'))
    entries[('File', 'max_file_size_mb')] = max_size_entry
    
    def save_config():
        """保存配置"""
        try:
            # 收集所有输入框的值
            for (section, option), entry in entries.items():
                value = entry.get().strip()
                config_manager.update(section, option, value)
            
            # 保存配置
            config_manager.save_config()
            
            if on_save:
                on_save()
            
            messagebox.showinfo("成功", "配置已保存")
            dialog.destroy()
        except Exception as e:
            messagebox.showerror("错误", f"保存配置时出错: {str(e)}")
    
    # 按钮框架
    button_frame = ttk.Frame(main_frame)
    button_frame.pack(fill=tk.X, pady=10)
    
    # 保存按钮
    ttk.Button(button_frame, text="保存", command=save_config).pack(side=tk.RIGHT, padx=5)
    
    # 取消按钮
    ttk.Button(button_frame, text="取消", command=dialog.destroy).pack(side=tk.RIGHT, padx=5)
    
    # 设置模态
    dialog.transient(parent)
    dialog.grab_set()


# ──────────────────────────────────────────────────────────────
#  云端同步管理对话框
# ──────────────────────────────────────────────────────────────

SYNC_FILES = [
    {
        "name": "条码映射",
        "remote": "barcode_mappings.json",
        "local": "config/barcode_mappings.json",
        "type": "json",
    },
    {
        "name": "供应商配置",
        "remote": "suppliers_config.json",
        "local": "config/suppliers_config.json",
        "type": "json",
    },
    {
        "name": "商品资料",
        "remote": "templates/商品资料.xlsx",
        "local": "templates/商品资料.xlsx",
        "type": "binary",
    },
    {
        "name": "采购单模板",
        "remote": "templates/银豹-采购单模板.xls",
        "local": "templates/银豹-采购单模板.xls",
        "type": "binary",
    },
    {
        "name": "商品记忆库 (DB)",
        "remote": "product_cache.db",
        "local": "data/product_cache.db",
        "type": "binary",
    },
]


def _format_size(path: str) -> str:
    try:
        size = os.path.getsize(path)
        if size < 1024 * 1024:
            return f"{size / 1024:.1f} KB"
        return f"{size / (1024 * 1024):.1f} MB"
    except OSError:
        return "—"


def show_cloud_sync_dialog(parent=None):
    """统一云端同步管理对话框"""

    sync = GiteaSync.from_config(ConfigManager())
    if sync is None:
        messagebox.showwarning(
            "配置不完整",
            "请先在「系统设置」中配置 Gitea 地址和 Access Token",
        )
        return

    dlg = tk.Toplevel(parent)
    dlg.title("云端同步管理")
    dlg.geometry("620x440")
    dlg.resizable(False, False)

    # 居中
    dlg.update_idletasks()
    x = (dlg.winfo_screenwidth() - 620) // 2
    y = (dlg.winfo_screenheight() - 440) // 2
    dlg.geometry(f"620x440+{x}+{y}")

    # ── Treeview ──
    columns = ("name", "local_status", "cloud_status")
    tree = ttk.Treeview(dlg, columns=columns, show="headings", height=6)
    tree.heading("name", text="文件")
    tree.heading("local_status", text="本地状态")
    tree.heading("cloud_status", text="云端状态")
    tree.column("name", width=140)
    tree.column("local_status", width=220)
    tree.column("cloud_status", width=220)
    tree.pack(fill=tk.BOTH, expand=True, padx=16, pady=(16, 8))

    # tag 颜色
    tree.tag_configure("synced", foreground="#2e7d32")
    tree.tag_configure("cloud_only", foreground="#e65100")
    tree.tag_configure("local_only", foreground="#1565c0")
    tree.tag_configure("missing", foreground="#999999")

    # 用 iid = remote_path 标识每行
    cloud_sha_cache: dict = {}  # remote_path -> sha

    def _load_local_status():
        """仅加载本地状态，不发网络请求"""
        for item in tree.get_children():
            tree.delete(item)
        for entry in SYNC_FILES:
            local = entry["local"]
            if os.path.exists(local):
                if entry["type"] == "json":
                    try:
                        with open(local, "r", encoding="utf-8") as f:
                            data = json.load(f)
                        if isinstance(data, dict):
                            local_text = f"{len(data)} 项"
                        elif isinstance(data, list):
                            local_text = f"{len(data)} 条记录"
                        else:
                            local_text = "已存在"
                    except Exception:
                        local_text = "已存在(解析异常)"
                else:
                    local_text = _format_size(local)
                tag = "local_only"
            else:
                local_text = "不存在"
                tag = "missing"
            tree.insert(
                "", tk.END,
                iid=entry["remote"],
                values=(entry["name"], local_text, "点「刷新状态」检查"),
                tags=(tag,),
            )

    def refresh_status():
        """刷新每行的本地/云端状态"""
        cloud_sha_cache.clear()
        for item in tree.get_children():
            tree.delete(item)

        for entry in SYNC_FILES:
            remote = entry["remote"]
            local = entry["local"]

            # 本地状态
            if os.path.exists(local):
                if entry["type"] == "json":
                    try:
                        with open(local, "r", encoding="utf-8") as f:
                            data = json.load(f)
                        if isinstance(data, dict):
                            local_text = f"{len(data)} 项"
                        elif isinstance(data, list):
                            local_text = f"{len(data)} 条记录"
                        else:
                            local_text = "已存在"
                    except Exception:
                        local_text = "已存在(解析异常)"
                else:
                    local_text = _format_size(local)
            else:
                local_text = "不存在"

            # 云端状态 — 网络请求，可能慢
            sha = sync.file_exists(remote)
            if sha:
                cloud_sha_cache[remote] = sha
                cloud_text = "已存在"
            else:
                cloud_text = "未上传"

            # tag
            local_ok = os.path.exists(local)
            cloud_ok = sha is not None
            if local_ok and cloud_ok:
                tag = "synced"
            elif cloud_ok and not local_ok:
                tag = "cloud_only"
            elif local_ok and not cloud_ok:
                tag = "local_only"
            else:
                tag = "missing"

            tree.insert(
                "", tk.END,
                iid=remote,
                values=(entry["name"], local_text, cloud_text),
                tags=(tag,),
            )

    # ── 操作函数 ──
    def _get_selected_entries():
        """获取选中的文件条目列表"""
        selected = tree.selection()
        if not selected:
            messagebox.showinfo("提示", "请先选中要操作的文件")
            return []
        return [e for e in SYNC_FILES if e["remote"] in selected]

    def push_selected():
        entries = _get_selected_entries()
        if not entries:
            return
        ok, fail = 0, 0
        for entry in entries:
            local, remote = entry["local"], entry["remote"]
            if not os.path.exists(local):
                messagebox.showwarning("跳过", f"本地文件不存在: {local}")
                fail += 1
                continue

            if entry["type"] == "json":
                try:
                    with open(local, "r", encoding="utf-8") as f:
                        data = json.load(f)
                    sha = cloud_sha_cache.get(remote)
                    result = sync.push_json(remote, data, f"同步 {entry['name']}", sha=sha)
                except Exception as e:
                    messagebox.showerror("推送失败", f"{entry['name']}: {e}")
                    fail += 1
                    continue
            else:
                result = sync.push_binary(remote, local, f"同步 {entry['name']}")

            if result:
                ok += 1
            else:
                fail += 1

        if ok:
            messagebox.showinfo("推送完成", f"成功 {ok} 个" + (f"，失败 {fail} 个" if fail else ""))
        refresh_status()

    def pull_selected():
        entries = _get_selected_entries()
        if not entries:
            return
        ok, fail = 0, 0
        for entry in entries:
            remote, local = entry["remote"], entry["local"]

            if entry["type"] == "json":
                result = sync.pull_json(remote)
                if result is None:
                    messagebox.showwarning("拉取失败", f"云端文件不存在: {entry['name']}")
                    fail += 1
                    continue
                content, sha = result
                # 写入本地
                os.makedirs(os.path.dirname(local) or ".", exist_ok=True)
                with open(local, "w", encoding="utf-8") as f:
                    json.dump(content, f, ensure_ascii=False, indent=2)
                # 特殊后处理
                _post_pull(entry, content)
            else:
                result = sync.pull_file(remote)
                if result is None:
                    messagebox.showwarning("拉取失败", f"云端文件不存在: {entry['name']}")
                    fail += 1
                    continue
                content, sha = result
                os.makedirs(os.path.dirname(local) or ".", exist_ok=True)
                with open(local, "wb") as f:
                    f.write(content)

            ok += 1

        if ok:
            messagebox.showinfo("拉取完成", f"成功 {ok} 个" + (f"，失败 {fail} 个" if fail else ""))
        refresh_status()

    def _post_pull(entry, data):
        """拉取 JSON 文件后的特殊处理"""
        if entry["remote"] == "barcode_mappings.json":
            try:
                from app.core.excel.converter import UnitConverter
                UnitConverter().update_barcode_mappings(data)
            except Exception:
                pass
        elif entry["remote"] == "suppliers_config.json":
            try:
                from app.services.processor_service import ProcessorService
                ProcessorService(ConfigManager()).reload_processors()
            except Exception:
                pass
        elif entry["remote"] == "product_memory.json":
            try:
                from app.core.db.product_db import ProductDatabase
                cfg = ConfigManager()
                db_path = cfg.get_path('Paths', 'product_db', fallback='data/product_cache.db') if hasattr(cfg, 'get_path') else 'data/product_cache.db'
                tpl_folder = cfg.get('Paths', 'template_folder', fallback='templates')
                item_data = cfg.get('Templates', 'item_data', fallback='商品资料.xlsx')
                tpl_path = os.path.join(tpl_folder, item_data)
                db = ProductDatabase(db_path, tpl_path)
                count = db.import_from_sync(data)
                logger.info(f"从云端导入商品记忆: {count} 条")
            except Exception:
                pass

    def push_all():
        ok, fail = 0, 0
        for entry in SYNC_FILES:
            local, remote = entry["local"], entry["remote"]
            if not os.path.exists(local):
                fail += 1
                continue
            if entry["type"] == "json":
                try:
                    with open(local, "r", encoding="utf-8") as f:
                        data = json.load(f)
                    sha = cloud_sha_cache.get(remote)
                    result = sync.push_json(remote, data, f"批量同步 {entry['name']}", sha=sha)
                except Exception:
                    fail += 1
                    continue
            else:
                result = sync.push_binary(remote, local, f"批量同步 {entry['name']}")
            if result:
                ok += 1
            else:
                fail += 1
        messagebox.showinfo("批量推送完成", f"成功 {ok} 个，失败 {fail} 个")
        refresh_status()

    def pull_all():
        ok, fail = 0, 0
        for entry in SYNC_FILES:
            remote, local = entry["remote"], entry["local"]
            if entry["type"] == "json":
                result = sync.pull_json(remote)
                if result is None:
                    fail += 1
                    continue
                content, sha = result
                os.makedirs(os.path.dirname(local) or ".", exist_ok=True)
                with open(local, "w", encoding="utf-8") as f:
                    json.dump(content, f, ensure_ascii=False, indent=2)
                _post_pull(entry, content)
            else:
                result = sync.pull_file(remote)
                if result is None:
                    fail += 1
                    continue
                content, sha = result
                os.makedirs(os.path.dirname(local) or ".", exist_ok=True)
                with open(local, "wb") as f:
                    f.write(content)
            ok += 1
        messagebox.showinfo("批量拉取完成", f"成功 {ok} 个，失败 {fail} 个")
        refresh_status()

    # ── 按钮区域 ──
    btn_frame = ttk.Frame(dlg)
    btn_frame.pack(fill=tk.X, padx=16, pady=(4, 16))

    # 左侧：批量操作
    ttk.Button(btn_frame, text="全部推送到云端", command=push_all).pack(side=tk.LEFT, padx=4)
    ttk.Button(btn_frame, text="全部从云端拉取", command=pull_all).pack(side=tk.LEFT, padx=4)

    # 右侧：选中操作 + 刷新 + 关闭
    ttk.Button(btn_frame, text="关闭", command=dlg.destroy).pack(side=tk.RIGHT, padx=4)
    ttk.Button(btn_frame, text="刷新状态", command=refresh_status).pack(side=tk.RIGHT, padx=4)
    tk.Button(btn_frame, text="推送到云端", command=push_selected, fg="white", bg="#4a90d9").pack(side=tk.RIGHT, padx=4)
    tk.Button(btn_frame, text="从云端拉取", command=pull_selected, fg="white", bg="#5cb85c").pack(side=tk.RIGHT, padx=4)

    # 仅显示本地状态，云端状态需手动点"刷新状态"
    _load_local_status()

    dlg.transient(parent)
    dlg.grab_set()