import logging
import threading
from datetime import datetime
import tkinter as tk
import ttkbootstrap as ttk


# 与 app.logging_setup 共享的映射；放在这里避免循环 import
_GUI_LEVEL_TO_LOG = {
    "debug": logging.DEBUG,
    "info": logging.INFO,
    "success": logging.INFO,
    "warning": logging.WARNING,
    "error": logging.ERROR,
    "separator": logging.INFO,
    "blank": logging.DEBUG,
}


def _theme_colors():
    style = ttk.Style()
    colors = style.colors
    return {
        "bg": colors.bg,
        "fg": colors.fg,
        "muted": colors.secondary,
        "accent": colors.info,
        "warning": colors.warning,
        "error": colors.danger,
        "success": colors.success,
        "text_bg": colors.inputbg,
        "text_fg": colors.inputfg,
    }


def _normalize_hex(hex_color: str, fallback: str = "#808080") -> str:
    """把颜色字符串规范化为 #RRGGBB；非法输入回退到 fallback。"""
    if not isinstance(hex_color, str):
        return fallback
    c = hex_color.strip()
    if not c:
        return fallback
    if c.startswith("#"):
        c = c[1:]
    if len(c) == 3:
        c = "".join(ch * 2 for ch in c)
    if len(c) != 6:
        return fallback
    try:
        int(c, 16)
    except ValueError:
        return fallback
    return f"#{c}"


def _hex_to_rgb(hex_color: str):
    c = _normalize_hex(hex_color, fallback="#808080").lstrip("#")
    return int(c[0:2], 16), int(c[2:4], 16), int(c[4:6], 16)


def _mix(hex_a: str, hex_b: str, ratio: float) -> str:
    ratio = max(0.0, min(1.0, ratio))
    r1, g1, b1 = _hex_to_rgb(hex_a)
    r2, g2, b2 = _hex_to_rgb(hex_b)
    r = int(r1 * (1 - ratio) + r2 * ratio)
    g = int(g1 * (1 - ratio) + g2 * ratio)
    b = int(b1 * (1 - ratio) + b2 * ratio)
    return f"#{r:02x}{g:02x}{b:02x}"


def _is_dark(hex_color: str) -> bool:
    r, g, b = _hex_to_rgb(hex_color)
    return (r * 299 + g * 587 + b * 114) / 1000 < 128


def _auto_text_color(bg_hex: str, fg_hint: str) -> str:
    """根据背景亮度给出稳定可读的文本颜色。"""
    bg_hex = _normalize_hex(bg_hex, fallback="#f5f5f5")
    fg_hint = _normalize_hex(fg_hint, fallback="#202020")
    if _is_dark(bg_hex):
        return _mix("#ffffff", fg_hint, 0.25)
    return _mix("#000000", fg_hint, 0.25)


class PQLogGUI(ttk.Frame):
    VALID_LEVELS = {"info", "success", "warning", "error", "debug", "separator", "blank"}

    def __init__(self, parent):
        super().__init__(parent)
        self._line_count = 0
        self._max_lines = 1500
        # 与 stdlib logging 联通的 logger，由 logging_setup 配置 file handler
        self._logger = logging.getLogger("pq.gui")
        self.create_widgets()

    def create_widgets(self):
        log_frame = ttk.LabelFrame(self, text="测试日志")
        log_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        toolbar = ttk.Frame(log_frame)
        toolbar.pack(fill=tk.X, padx=6, pady=(6, 2))

        self.log_summary_var = tk.StringVar(value="0 条日志")
        ttk.Label(
            toolbar,
            textvariable=self.log_summary_var,
            bootstyle="secondary",
        ).pack(side=tk.LEFT)

        ttk.Button(
            toolbar,
            text="清空日志",
            command=self.clear_log,
            bootstyle="secondary-outline",
            width=10,
        ).pack(side=tk.RIGHT)

        text_container = ttk.Frame(log_frame)
        text_container.pack(fill=tk.BOTH, expand=True, padx=6, pady=(0, 6))

        palette = _theme_colors()
        self.log_text = tk.Text(
            text_container,
            height=10,
            width=50,
            wrap=tk.WORD,
            font=("Consolas", 10),
            bg=palette["text_bg"],
            fg=palette["text_fg"],
            relief=tk.FLAT,
            bd=0,
            padx=10,
            pady=8,
            spacing1=2,
            spacing3=2,
            insertbackground=palette["text_fg"],
        )
        self.log_text.pack(fill=tk.BOTH, expand=True, side=tk.LEFT)

        log_scrollbar = ttk.Scrollbar(text_container, command=self.log_text.yview)
        log_scrollbar.pack(fill=tk.Y, side=tk.RIGHT)
        self.log_text.config(yscrollcommand=log_scrollbar.set)

        self._configure_tags()
        self.log_text.config(state=tk.DISABLED)

    def log(self, message, level="info", _from_logging=False):
        if threading.current_thread() is not threading.main_thread():
            self.after(0, self.log, message, level, _from_logging)
            return

        text = "" if message is None else str(message)
        normalized_level = self._normalize_level(level, text)

        # 转发到 stdlib logging，落到文件（_from_logging=True 表示来源已是
        # logging，不再回写避免重复）。带 _from_gui 标记，TkLogHandler 会跳过。
        if not _from_logging and normalized_level != "blank":
            log_level = _GUI_LEVEL_TO_LOG.get(normalized_level, logging.INFO)
            try:
                self._logger.log(
                    log_level, text, extra={"_from_gui": True}
                )
            except Exception:
                pass

        self.log_text.config(state=tk.NORMAL)
        self._append_message(text, normalized_level)
        self.log_text.see(tk.END)
        self.log_text.config(state=tk.DISABLED)

    def clear_log(self):
        if threading.current_thread() is not threading.main_thread():
            self.after(0, self.clear_log)
            return

        self.log_text.config(state=tk.NORMAL)
        self.log_text.delete(1.0, tk.END)
        self.log_text.config(state=tk.DISABLED)
        self._line_count = 0
        self._update_summary()

    def _configure_tags(self):
        palette = _theme_colors()
        bg = self.log_text.cget("bg") or palette["text_bg"] or palette["bg"]
        base_fg = _auto_text_color(bg, palette["fg"])
        muted_fg = _mix(base_fg, bg, 0.45)
        debug_level_color = _mix(palette["accent"], base_fg, 0.35)
        debug_msg_color = _mix(palette["accent"], base_fg, 0.50)
        self.log_text.tag_configure("timestamp", foreground=palette["muted"])
        self.log_text.tag_configure("level_info", foreground=palette["accent"])
        self.log_text.tag_configure("level_success", foreground=palette["success"])
        self.log_text.tag_configure("level_warning", foreground=palette["warning"])
        self.log_text.tag_configure("level_error", foreground=palette["error"])
        self.log_text.tag_configure("level_debug", foreground=debug_level_color)
        self.log_text.tag_configure("message", foreground=base_fg)
        self.log_text.tag_configure("message_success", foreground=palette["success"])
        self.log_text.tag_configure("message_warning", foreground=palette["warning"])
        self.log_text.tag_configure("message_error", foreground=palette["error"])
        self.log_text.tag_configure("message_debug", foreground=debug_msg_color)
        self.log_text.tag_configure("separator", foreground=muted_fg)
        self.log_text.tag_configure("traceback", foreground=palette["error"])
        self.log_text.tag_configure("blank", spacing1=4, spacing3=4)

    def refresh_log_theme(self):
        """主题切换后刷新日志控件的背景和字体颜色。"""
        if threading.current_thread() is not threading.main_thread():
            self.after(0, self.refresh_log_theme)
            return

        palette = _theme_colors()
        bg = palette["text_bg"]
        fg_hint = palette["text_fg"] if palette["text_fg"] else palette["fg"]
        fg = _auto_text_color(bg, fg_hint)

        self.log_text.configure(
            bg=bg,
            fg=fg,
            insertbackground=fg,
        )
        self._configure_tags()

    def _append_message(self, message, level):
        lines = message.splitlines() or [""]
        for line in lines:
            self._append_line(line, level)
        self._trim_excess_lines()
        self._update_summary()

    def _append_line(self, line, level):
        timestamp = datetime.now().strftime("%H:%M:%S")
        rendered = "" if line is None else str(line).strip()

        if level == "blank" or not rendered:
            self.log_text.insert(tk.END, "\n", ("blank",))
            self._line_count += 1
            return

        if level == "separator":
            self.log_text.insert(tk.END, f"[{timestamp}] ", ("timestamp",))
            self.log_text.insert(tk.END, "[SECTION] ", ("level_info",))
            self.log_text.insert(tk.END, rendered + "\n", ("separator",))
            self._line_count += 1
            return

        level_tag = f"level_{level}"
        level_label = level.upper().ljust(7)
        if level == "error" and rendered.startswith("Traceback"):
            message_tag = "traceback"
        elif level in {"success", "warning", "error", "debug"}:
            message_tag = f"message_{level}"
        else:
            message_tag = "message"

        self.log_text.insert(tk.END, f"[{timestamp}] ", ("timestamp",))
        self.log_text.insert(tk.END, f"[{level_label}] ", (level_tag,))
        self.log_text.insert(tk.END, rendered + "\n", (message_tag,))
        self._line_count += 1

    def _normalize_level(self, level, message):
        normalized = "info" if level is None else str(level).strip().lower()
        if normalized not in self.VALID_LEVELS:
            normalized = "info"

        if normalized == "info" and (message is None or str(message).strip() == ""):
            return "blank"

        return normalized

    def _trim_excess_lines(self):
        overflow = self._line_count - self._max_lines
        if overflow <= 0:
            return

        self.log_text.delete("1.0", f"{overflow + 1}.0")
        self._line_count = self._max_lines

    def _update_summary(self):
        self.log_summary_var.set(f"{self._line_count} 条日志")