"""
主窗口 UI 组件
"""
import os
import sys
import threading
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import openpyxl

# ---- 配色 ----
C = {
    "bg":            "#f0f2f5",
    "white":         "#ffffff",
    "text":          "#1a1a2e",
    "text_muted":    "#6b7280",
    "accent":        "#4f46e5",
    "accent_hover":  "#4338ca",
    "accent_light":  "#eef2ff",
    "border":        "#e5e7eb",
    "success":       "#059669",
}

# 列宽
COL_W = [220, 1, 220, 1, 220, 1, 200]
COL_X = [0]
for w in COL_W[:-1]:
    COL_X.append(COL_X[-1] + w)

ROW_H = 44
CELL_PAD = 8


# ======================================================================
# 自定义下拉组件
# ======================================================================
class Dropdown(tk.Frame):
    """纯自定义下拉选择器 — 不用任何系统 Combobox"""

    def __init__(self, parent, values=None, default="", on_select=None,
                 font=("Microsoft YaHei", 10), width=180, **kw):
        super().__init__(parent, bg=C["white"], highlightbackground=C["border"],
                         highlightthickness=1, cursor="hand2", **kw)
        self.values = values or []
        self._selected = default or ""
        self._on_select = on_select
        self._font = font
        self._popup = None
        self._hover = False

        # 文字标签
        self._label = tk.Label(
            self, text=self._selected or "请选择",
            font=font, bg=C["white"], fg=C["text"] if self._selected else C["text_muted"],
            anchor=tk.W, padx=8,
        )
        self._label.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # 箭头
        self._arrow = tk.Label(
            self, text="▾", font=("Microsoft YaHei", 8),
            bg=C["white"], fg="#9ca3af", padx=6,
        )
        self._arrow.pack(side=tk.RIGHT, fill=tk.Y)

        # 事件绑定
        for w in (self, self._label, self._arrow):
            w.bind("<Button-1>", self._toggle)
            w.bind("<Enter>", self._on_enter)
            w.bind("<Leave>", self._on_leave)

    def _on_enter(self, _):
        self._hover = True
        self.configure(highlightbackground=C["accent"])
        self._label.configure(bg="#f9fafb")
        self._arrow.configure(bg="#f9fafb", fg=C["accent"])

    def _on_leave(self, _):
        self._hover = False
        if not self._popup:
            self.configure(highlightbackground=C["border"])
        self._label.configure(bg=C["white"])
        self._arrow.configure(bg=C["white"], fg="#9ca3af")

    def _toggle(self, event=None):
        if self._popup:
            self._close()
        else:
            self._open()

    def _open(self):
        self.configure(highlightbackground=C["accent"])
        self._label.configure(bg="#f9fafb")
        self._arrow.configure(bg="#f9fafb", fg=C["accent"])

        self._popup = tk.Toplevel(self)
        self._popup.overrideredirect(True)
        self._popup.configure(bg=C["white"])

        # 计算位置
        x = self.winfo_rootx()
        y = self.winfo_rooty() + self.winfo_height() + 1
        w = self.winfo_width()
        item_h = 32
        max_h = min(len(self.values) * item_h, 240)
        list_h = max(item_h, max_h)

        self._popup.geometry(f"{w}x{list_h}+{x}+{y}")

        # 外边框
        border = tk.Frame(self._popup, bg=C["border"])
        border.pack(fill=tk.BOTH, expand=True)

        # 列表
        self._listbox = tk.Listbox(
            border, font=self._font,
            bg=C["white"], fg=C["text"],
            selectbackground=C["accent_light"],
            selectforeground=C["accent"],
            selectborderwidth=0,
            activestyle="none",
            highlightthickness=0,
            borderwidth=0,
            relief=tk.FLAT,
            height=len(self.values),
        )
        self._listbox.pack(fill=tk.BOTH, expand=True, padx=1, pady=1)

        for i, v in enumerate(self.values):
            self._listbox.insert(tk.END, f"  {v}")
            if v == self._selected:
                self._listbox.selection_set(i)
                self._listbox.see(i)

        self._listbox.bind("<ButtonRelease-1>", self._on_pick)
        self._listbox.bind("<Return>", self._on_pick)
        self._listbox.bind("<Escape>", lambda e: self._close())
        self._listbox.focus_set()

        # 点击外部关闭
        self._popup.bind("<FocusOut>", lambda e: self.after(100, self._check_focus))

        # 绑定全局点击关闭（鼠标点击 popup 外）
        self._grab_bind = self.winfo_toplevel().bind(
            "<Button-1>", lambda e: self._on_global_click(e), add="+"
        )

    def _on_global_click(self, event):
        if self._popup:
            px, py = event.x_root, event.y_root
            px2 = self._popup.winfo_rootx()
            py2 = self._popup.winfo_rooty()
            pw = self._popup.winfo_width()
            ph = self._popup.winfo_height()
            if not (px2 <= px <= px2 + pw and py2 <= py <= py2 + ph):
                self._close()

    def _check_focus(self):
        if self._popup and not self._popup.focus_displayof():
            self._close()

    def _close(self, *_):
        if self._popup:
            self._popup.destroy()
            self._popup = None
            self._listbox = None
            self.configure(highlightbackground=C["border"])
            self._label.configure(bg=C["white"])
            self._arrow.configure(bg=C["white"], fg="#9ca3af")
            # 移除全局绑定
            try:
                self.winfo_toplevel().unbind("<Button-1>", self._grab_bind)
            except Exception:
                pass

    def _on_pick(self, event):
        sel = self._listbox.curselection()
        if sel:
            idx = sel[0]
            self._selected = self.values[idx]
            self._label.configure(text=self._selected, fg=C["text"])
            if self._on_select:
                self._on_select(self._selected)
        self._close()

    # ── 公开 API ──
    def get(self):
        return self._selected

    def set_values(self, values, default=None):
        self.values = values
        if default is not None:
            self._selected = default
            self._label.configure(text=default, fg=C["text"] if default else C["text_muted"])
        # 如果已打开则重建
        if self._popup:
            self._close()
            self._open()


# ======================================================================
# 主窗口
# ======================================================================
class MainWindow:
    """应用程序主窗口"""

    def __init__(self, root: tk.Tk):
        self.root = root
        self.root.configure(bg=C["bg"])
        self._files_data = []
        self._build_ui()

    def _build_ui(self):
        # ── 选择卡片 ──
        card = tk.Frame(self.root, bg=C["white"])
        card.pack(fill=tk.X, padx=24, pady=(20, 0))

        card_border = tk.Frame(card, bg=C["white"],
                               highlightbackground=C["border"],
                               highlightthickness=1)
        card_border.pack(fill=tk.X)

        tk.Label(
            card_border, text="📂  选择 Excel 文件",
            font=("Microsoft YaHei", 12, "bold"),
            bg=C["white"], fg=C["text"], anchor=tk.W,
        ).pack(fill=tk.X, padx=20, pady=(16, 10))

        input_row = tk.Frame(card_border, bg=C["white"])
        input_row.pack(fill=tk.X, padx=20, pady=(0, 10))

        entry_border = tk.Frame(
            input_row, bg=C["white"],
            highlightbackground=C["border"], highlightthickness=1,
        )
        entry_border.pack(side=tk.LEFT, fill=tk.X, expand=True)

        self._path_entry = tk.Entry(
            entry_border,
            font=("Microsoft YaHei", 10),
            bg="#f9fafb", fg=C["text_muted"],
            relief=tk.FLAT, borderwidth=0,
            readonlybackground="#f9fafb",
        )
        self._path_entry.pack(fill=tk.X, padx=10, pady=8)
        self._path_entry.insert(0, "请选择 Excel 文件（.xlsx / .xls，可多选）")
        self._path_entry.configure(state="readonly")

        self._browse_btn = tk.Button(
            input_row, text="浏览...",
            font=("Microsoft YaHei", 10, "bold"),
            bg=C["accent"], fg="white", relief=tk.FLAT,
            activebackground=C["accent_hover"], activeforeground="white",
            cursor="hand2", padx=20, pady=7, borderwidth=0,
            command=self._on_select_files,
        )
        self._browse_btn.pack(side=tk.LEFT, padx=(8, 0))

        status_bar = tk.Frame(card_border, bg="#f9fafb")
        status_bar.pack(fill=tk.X)

        self._status_label = tk.Label(
            status_bar, text="  未选择任何文件",
            font=("Microsoft YaHei", 9),
            bg="#f9fafb", fg=C["text_muted"], anchor=tk.W,
        )
        self._status_label.pack(side=tk.LEFT, padx=16, pady=(6, 6))

        # ── 表格区域 ──
        self._table_area = tk.Frame(self.root, bg=C["bg"])
        self._table_area.pack(fill=tk.BOTH, expand=True, padx=24, pady=(12, 0))

        # ── 日志面板（底部固定高度，内部独立滚动） ──
        self._log_frame = tk.Frame(self.root, bg="white", height=130)
        self._log_frame.pack(fill=tk.X, padx=24, pady=(8, 8))
        self._log_frame.pack_propagate(False)

        log_header = tk.Frame(self._log_frame, bg="white")
        log_header.pack(fill=tk.X)
        tk.Label(log_header, text="📋  处理日志",
                 font=("Microsoft YaHei", 9, "bold"),
                 bg="white", fg=C["text"], anchor=tk.W,
        ).pack(side=tk.LEFT, padx=10, pady=(6, 2))

        log_text_frame = tk.Frame(self._log_frame, bg="white")
        log_text_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 8))

        self._log_text = tk.Text(
            log_text_frame,
            font=("Consolas", 9),
            bg="#f9fafb", fg=C["text"],
            wrap=tk.WORD, state=tk.DISABLED,
            relief=tk.FLAT, borderwidth=0,
            height=6,
        )
        log_scrollbar = tk.Scrollbar(log_text_frame, orient=tk.VERTICAL,
                                     command=self._log_text.yview)
        self._log_text.configure(yscrollcommand=log_scrollbar.set)
        log_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self._log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        self._show_empty()

    # ==================================================================
    # 空状态
    # ==================================================================
    def _show_empty(self):
        for w in self._table_area.winfo_children():
            w.destroy()

        drop = tk.Frame(self._table_area, bg="#fafafe",
                        highlightbackground="#c7d2fe",
                        highlightthickness=2)
        drop.pack(fill=tk.BOTH, expand=True)

        inner = tk.Frame(drop, bg="#fafafe")
        inner.place(relx=0.5, rely=0.5, anchor=tk.CENTER)

        tk.Label(inner, text="📂", font=("Segoe UI Emoji", 40),
                 bg="#fafafe", fg="#c7d2fe").pack(pady=(0, 4))
        tk.Label(inner, text="点击上方「浏览...」按钮选择文件",
                 font=("Microsoft YaHei", 13, "bold"),
                 bg="#fafafe", fg=C["text_muted"]).pack()
        tk.Label(inner, text="支持 .xlsx、.xls 格式，可同时选择多个文件",
                 font=("Microsoft YaHei", 9),
                 bg="#fafafe", fg="#c0c4cc").pack(pady=(2, 0))

    # ==================================================================
    # 读取 xlsx 信息（用内置 zipfile，避免 openpyxl 打包问题）
    # ==================================================================
    def _read_xlsx_info(self, filepath):
        import pandas as pd
        sheet_names = []
        columns = []
        for engine in ["openpyxl", "xlrd"]:
            try:
                xls = pd.ExcelFile(filepath, engine=engine)
                sheet_names = xls.sheet_names
                if sheet_names:
                    df = pd.read_excel(xls, sheet_name=sheet_names[0], nrows=1)
                    columns = [str(c) for c in df.columns.tolist()]
                xls.close()
                break
            except Exception:
                continue
        return sheet_names, columns

    # ==================================================================
    # 选择文件
    # ==================================================================
    def _on_select_files(self):
        paths = filedialog.askopenfilenames(
            title="选择 Excel 文件",
            filetypes=[
                ("Excel 文件", "*.xlsx *.xls"),
                ("Excel 2007+", "*.xlsx"),
                ("Excel 97-2003", "*.xls"),
            ],
        )
        if not paths:
            return

        for p in paths:
            if any(d["path"] == p for d in self._files_data):
                continue
            try:
                sheet_names, columns = self._read_xlsx_info(p)
                first_sheet = sheet_names[0] if sheet_names else None
                self._files_data.append({
                    "path": p,
                    "filename": os.path.basename(p),
                    "sheet_names": sheet_names,
                    "columns": columns,
                    "selected_sheet": first_sheet,
                    "selected_col": columns[0] if columns else None,
                    "parse_mode": "横向排列",
                })
            except Exception as e:
                messagebox.showerror("读取失败", f"无法读取文件:\n{os.path.basename(p)}\n\n{e}")
                continue

        n = len(self._files_data)
        if n == 0:
            self._path_entry.configure(state="normal")
            self._path_entry.delete(0, tk.END)
            self._path_entry.insert(0, "请选择 Excel 文件（.xlsx / .xls，可多选）")
            self._path_entry.configure(state="readonly", fg=C["text_muted"])
            self._status_label.configure(text="  未选择任何文件")
        elif n == 1:
            self._path_entry.configure(state="normal")
            self._path_entry.delete(0, tk.END)
            self._path_entry.insert(0, self._files_data[0]["path"])
            self._path_entry.configure(state="readonly", fg=C["text"])
            self._status_label.configure(
                text=f"  已选择 1 个文件  |  点击「浏览...」可继续添加",
                fg=C["success"],
            )
        else:
            self._path_entry.configure(state="normal")
            self._path_entry.delete(0, tk.END)
            self._path_entry.insert(0, f"已选择 {n} 个文件")
            self._path_entry.configure(state="readonly", fg=C["text"])
            self._status_label.configure(
                text=f"  已选择 {n} 个文件  |  点击「浏览...」可继续添加",
                fg=C["success"],
            )

        if self._files_data:
            self._build_table()
        else:
            self._show_empty()

    # ==================================================================
    # 构建表格
    # ==================================================================
    def _build_table(self):
        for w in self._table_area.winfo_children():
            w.destroy()

        self._table_frame = tk.Frame(self._table_area, bg=C["bg"])
        self._table_frame.pack(fill=tk.BOTH, expand=True)

        self._draw_header()
        for fi, fd in enumerate(self._files_data):
            self._draw_row(fi, fd)

        # ── 确认按钮 ──
        btn_bar = tk.Frame(self._table_frame, bg=C["bg"])
        btn_bar.pack(fill=tk.X, pady=(16, 0))

        self._confirm_btn = tk.Button(
            btn_bar, text="✅  开始解析",
            font=("Microsoft YaHei", 12, "bold"),
            bg=C["accent"], fg="white", relief=tk.FLAT,
            activebackground=C["accent_hover"], activeforeground="white",
            cursor="hand2", padx=28, pady=10, borderwidth=0,
            command=self._on_confirm,
        )
        self._confirm_btn.pack(side=tk.RIGHT)

        self._progress_label = tk.Label(
            btn_bar, text="",
            font=("Microsoft YaHei", 9),
            bg=C["bg"], fg=C["text_muted"], anchor=tk.E,
        )
        self._progress_label.pack(side=tk.RIGHT, padx=(0, 12))

    def _draw_header(self):
        hdr = tk.Frame(self._table_frame, bg=C["white"], height=38)
        hdr.pack(fill=tk.X)
        hdr.pack_propagate(False)

        headers = ["文件名", "工作表", "解析列", "解析方式"]
        for i, title in enumerate(headers):
            col_idx = i * 2
            x, w = COL_X[col_idx], COL_W[col_idx]
            tk.Label(
                hdr, text=title, font=("Microsoft YaHei", 10, "bold"),
                bg=C["white"], fg=C["text"], anchor=tk.W,
            ).place(x=x + CELL_PAD, y=0, width=w - CELL_PAD * 2, height=38)
            if i < 3:
                tk.Frame(hdr, bg=C["border"], width=1).place(
                    x=COL_X[col_idx + 1], y=6, height=26
                )

        tk.Frame(self._table_frame, bg=C["border"], height=1).pack(fill=tk.X)

    def _draw_row(self, fi: int, fd: dict):
        row = tk.Frame(self._table_frame, bg=C["white"], height=ROW_H)
        row.pack(fill=tk.X)
        row.pack_propagate(False)

        # ── 文件名 ──
        x, w = COL_X[0], COL_W[0]
        tk.Label(
            row, text=f"📄  {fd['filename']}",
            font=("Microsoft YaHei", 10), bg=C["white"], fg=C["text"], anchor=tk.W,
        ).place(x=x + CELL_PAD, y=0, width=w - CELL_PAD * 2, height=ROW_H)

        tk.Frame(row, bg=C["border"], width=1).place(x=COL_X[1], y=6, height=ROW_H - 12)

        # ── 工作表（自定义 Dropdown） ──
        x, w = COL_X[2], COL_W[2]
        dd_sheet = Dropdown(
            row,
            values=fd["sheet_names"],
            default=fd["selected_sheet"] or "",
            on_select=lambda val, i=fi: self._on_sheet_change(i, val),
        )
        dd_sheet.place(x=x + CELL_PAD, y=6, width=w - CELL_PAD * 2, height=ROW_H - 12)
        fd["sheet_dd"] = dd_sheet

        tk.Frame(row, bg=C["border"], width=1).place(x=COL_X[3], y=6, height=ROW_H - 12)

        # ── 解析列（自定义 Dropdown） ──
        x, w = COL_X[4], COL_W[4]
        dd_col = Dropdown(
            row,
            values=fd["columns"],
            default=fd["selected_col"] or "",
            on_select=lambda val, i=fi: self._on_col_change(i, val),
        )
        dd_col.place(x=x + CELL_PAD, y=6, width=w - CELL_PAD * 2, height=ROW_H - 12)
        fd["col_dd"] = dd_col

        tk.Frame(row, bg=C["border"], width=1).place(x=COL_X[5], y=6, height=ROW_H - 12)

        # ── 解析方式（自定义 Dropdown） ──
        x, w = COL_X[6], COL_W[6]
        dd_mode = Dropdown(
            row,
            values=["横向排列", "纵向排序"],
            default=fd["parse_mode"],
            on_select=lambda val, i=fi: self._on_mode_change(i, val),
        )
        dd_mode.place(x=x + CELL_PAD, y=6, width=w - CELL_PAD * 2, height=ROW_H - 12)
        fd["mode_dd"] = dd_mode

        fd["row_frame"] = row
        tk.Frame(self._table_frame, bg=C["border"], height=1).pack(fill=tk.X)

    # ==================================================================
    # 切换工作表 → 更新解析列
    # ==================================================================
    def _on_sheet_change(self, idx: int, new_sheet: str):
        fd = self._files_data[idx]
        fd["selected_sheet"] = new_sheet

        try:
            import pandas as pd
            for engine in ["openpyxl", "xlrd"]:
                try:
                    df = pd.read_excel(fd["path"], sheet_name=new_sheet, nrows=1, engine=engine)
                    columns = [str(c) for c in df.columns.tolist()]
                    break
                except Exception:
                    continue

            fd["columns"] = columns
            first_col = columns[0] if columns else None
            fd["selected_col"] = first_col
            fd["col_dd"].set_values(columns, default=first_col)
        except Exception as e:
            messagebox.showerror("错误", f"读取工作表失败: {e}")

    # ==================================================================
    # 下拉选择回调
    # ==================================================================
    def _on_col_change(self, idx: int, val: str):
        self._files_data[idx]["selected_col"] = val

    def _on_mode_change(self, idx: int, val: str):
        self._files_data[idx]["parse_mode"] = val

    # ==================================================================
    # 日志输出
    # ==================================================================
    def _log_write(self, text: str):
        """向底部日志面板追加一行"""
        self._log_text.configure(state=tk.NORMAL)
        self._log_text.insert(tk.END, text + "\n")
        self._log_text.see(tk.END)
        self._log_text.configure(state=tk.DISABLED)

    def _log_clear(self):
        """清空日志面板"""
        self._log_text.configure(state=tk.NORMAL)
        self._log_text.delete("1.0", tk.END)
        self._log_text.configure(state=tk.DISABLED)

    # ==================================================================
    # 确认解析
    # ==================================================================
    def _on_confirm(self):
        if not self._files_data:
            return

        self._confirm_btn.configure(state=tk.DISABLED, text="⏳  解析中...")
        self._log_clear()
        self._log_write("🚀 开始解析...")

        # 捕获 print 输出到日志面板
        class _LogWriter:
            def __init__(self, root, log_func):
                self._root = root
                self._log = log_func
                self._buf = ""
            def write(self, s):
                self._buf += s
                if "\n" in self._buf:
                    lines = self._buf.split("\n")
                    self._buf = lines[-1]
                    for line in lines[:-1]:
                        stripped = line.strip()
                        if stripped:
                            self._root.after(0, lambda t=stripped: self._log(t))
            def flush(self):
                if self._buf.strip():
                    self._root.after(0, lambda t=self._buf.strip(): self._log(t))
                    self._buf = ""

        def _run():
            import sys
            from app import finance_parse as fp
            total = len(self._files_data)
            success = 0
            old_stdout = sys.stdout
            old_stderr = sys.stderr
            log_writer = _LogWriter(self.root, self._log_write)
            sys.stdout = log_writer
            sys.stderr = open(os.devnull, "w")

            try:
                for i, fd in enumerate(self._files_data):
                    fname = fd["filename"]
                    self.root.after(0, lambda n=i+1, fn=fname:
                        self._log_write(f"⏳ [{n}/{total}] 正在处理: {fn}"))
                    try:
                        save_path, amount = fp.parse_finance_data(
                            file_path=fd["path"],
                            target_index=fd["col_dd"].get(),
                            is_horizontal=(fd["mode_dd"].get() == "横向排列"),
                            sheet_name=fd["sheet_dd"].get(),
                        )
                        success += 1
                        fd["result"] = save_path
                        fd["amount"] = amount
                        self.root.after(0, lambda fn=fname, sp=save_path, amt=amount:
                            self._log_write(f"✅ [{success}/{total}] 完成: {fn}  共 {amt} 条"))
                    except Exception as e:
                        fd["error"] = str(e)
                        self.root.after(0, lambda fn=fname, err=str(e):
                            self._log_write(f"❌ [{i+1}/{total}] 失败: {fn}  — {err}"))
            finally:
                sys.stdout = old_stdout
                sys.stderr = old_stderr

            self.root.after(0, lambda: self._on_parse_done(success, total))

        threading.Thread(target=_run, daemon=True).start()

    def _on_parse_done(self, success: int, total: int):
        self._confirm_btn.configure(state=tk.NORMAL, text="✅  开始解析")

        # 从列表中移除已成功解析的文件，保留失败的
        self._files_data = [fd for fd in self._files_data if "error" in fd]

        if success == total:
            self._log_write(f"🎉 全部 {total} 个文件解析完成！结果已保存到原文件所在目录。")
            messagebox.showinfo("解析完成",
                f"全部 {total} 个文件解析完成！\n\n结果已保存到原文件所在目录。")
        else:
            failed_count = len(self._files_data)
            self._log_write(f"⚠️ 完成 {success}/{total}，有 {failed_count} 个文件失败。")
            errors = []
            for fd in self._files_data:
                if "error" in fd:
                    errors.append(f"• {fd['filename']}: {fd['error']}")
            msg = f"完成 {success}/{total}\n\n失败:\n" + "\n".join(errors[:5])
            messagebox.showwarning("解析完成（部分失败）", msg)

        # 刷新 UI：清除成功项后重建表格或显示空状态
        if self._files_data:
            self._build_table()
            n = len(self._files_data)
            self._status_label.configure(
                text=f"  剩余 {n} 个失败文件  |  可调整配置后重新解析",
                fg="#dc2626",
            )
        else:
            self._show_empty()
            self._path_entry.configure(state="normal")
            self._path_entry.delete(0, tk.END)
            self._path_entry.insert(0, "请选择 Excel 文件（.xlsx / .xls，可多选）")
            self._path_entry.configure(state="readonly", fg=C["text_muted"])
            self._status_label.configure(text="  未选择任何文件")
