import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import threading
import subprocess
import os
import sys
import re
import json
import queue
from concurrent.futures import ThreadPoolExecutor, as_completed
from ebooklib import epub

# ================= 配置管理模块 =================
CONFIG_FILE = "sonovel_config.json"
DEFAULT_CONFIG = {
    "save_path": os.path.join(os.path.expanduser("~"), "Desktop", "SoNovelDownloads"),
    "max_workers": 3,
    "sources": [
        {"name": "笔趣阁(主)", "url": "https://www.bqg.cc", "status": "正常"},
        {"name": "顶点小说", "url": "https://www.23us.com", "status": "正常"},
        {"name": "测试源(失效)", "url": "https://test.com", "status": "失效"}
    ]
}

class ConfigManager:
    @staticmethod
    def load():
        if os.path.exists(CONFIG_FILE):
            with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
                return json.load(f)
        ConfigManager.save(DEFAULT_CONFIG)
        return DEFAULT_CONFIG

    @staticmethod
    def save(config):
        with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
            json.dump(config, f, ensure_ascii=False, indent=2)

# ================= 核心 GUI 类 =================
class SoNovelUltimateGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("So Novel Ultimate - 终极跨平台版")
        self.root.geometry("850x650")
        
        style = ttk.Style()
        style.theme_use('clam')
        style.configure("TButton", padding=6, font=("Microsoft YaHei", 10))
        style.configure("TLabel", font=("Microsoft YaHei", 10))
        style.configure("Treeview", rowheight=25, font=("Microsoft YaHei", 9))

        self.config = ConfigManager.load()
        self.save_path = tk.StringVar(value=self.config["save_path"])
        self.max_workers = tk.IntVar(value=self.config["max_workers"])
        self.sources = self.config["sources"]
        
        self.txt_path = tk.StringVar()
        self.is_running = False
        self.stop_event = threading.Event()
        self.task_queue = queue.Queue()
        self.exe_path = self._detect_executable()

        # 主标签页容器
        self.notebook = ttk.Notebook(root)
        self.notebook.pack(fill="both", expand=True, padx=5, pady=5)

        self._build_download_tab()
        self._build_batch_tab()
        self._build_tools_tab()
        self._build_sources_tab()
        self._build_status_bar()

    def _detect_executable(self):
        base_dir = os.path.dirname(os.path.abspath(__file__))
        return os.path.join(base_dir, "sonovel.exe") if sys.platform == "win32" else os.path.join(base_dir, "sonovel")

    # ================= 1. 下载与高级搜索标签页 =================
    def _build_download_tab(self):
        tab = ttk.Frame(self.notebook)
        self.notebook.add(tab, text="🔍 搜索与下载")
        
        frame = ttk.LabelFrame(tab, text="高级搜索与过滤", padding=10)
        frame.pack(fill="x", padx=10, pady=10)
        
        ttk.Label(frame, text="关键词:").grid(row=0, column=0, sticky="w")
        self.entry_search = ttk.Entry(frame, width=40)
        self.entry_search.grid(row=0, column=1, padx=5, pady=5)
        
        ttk.Label(frame, text="过滤字数:").grid(row=0, column=2, sticky="w", padx=5)
        self.entry_min_words = ttk.Entry(frame, width=8)
        self.entry_min_words.insert(0, "10000")
        self.entry_min_words.grid(row=0, column=3, padx=5)

        self.btn_download = ttk.Button(frame, text="搜索并下载", command=self.start_download)
        self.btn_download.grid(row=0, column=4, padx=5)
        
        ttk.Label(frame, text="保存路径:").grid(row=1, column=0, sticky="w", pady=5)
        ttk.Entry(frame, textvariable=self.save_path, width=40, state="readonly").grid(row=1, column=1, padx=5, pady=5)
        ttk.Button(frame, text="浏览...", command=self.select_save_path).grid(row=1, column=2, padx=5)

        # 日志区
        frame_log = ttk.LabelFrame(tab, text="📝 运行日志", padding=10)
        frame_log.pack(fill="both", expand=True, padx=10, pady=10)
        self.text_log = tk.Text(frame_log, height=10, font=("Consolas", 9), state="disabled", wrap="word")
        self.text_log.pack(side="left", fill="both", expand=True)
        scrollbar = ttk.Scrollbar(frame_log, orient="vertical", command=self.text_log.yview)
        scrollbar.pack(side="right", fill="y")
        self.text_log.config(yscrollcommand=scrollbar.set)

    # ================= 2. 批量下载队列标签页 =================
    def _build_batch_tab(self):
        tab = ttk.Frame(self.notebook)
        self.notebook.add(tab, text="📚 批量下载队列")

        self.batch_text = tk.Text(tab, height=8, font=("Consolas", 9))
        self.batch_text.pack(fill="both", expand=True, padx=10, pady=10)

        ctrl_frame = ttk.Frame(tab)
        ctrl_frame.pack(fill="x", padx=10, pady=5)
        ttk.Button(ctrl_frame, text="加入队列", command=self.add_to_queue).pack(side="left", padx=5)
        ttk.Button(ctrl_frame, text="开始批量下载", command=self.start_batch).pack(side="left", padx=5)
        ttk.Button(ctrl_frame, text="停止所有", command=self.stop_all).pack(side="left", padx=5)
        
        ttk.Label(ctrl_frame, text="并发数:").pack(side="right", padx=5)
        ttk.Spinbox(ctrl_frame, from_=1, to=10, textvariable=self.max_workers, width=5).pack(side="right")

    # ================= 3. TXT 净化与转换标签页 =================
    def _build_tools_tab(self):
        tab = ttk.Frame(self.notebook)
        self.notebook.add(tab, text="🔄 TXT 净化与转换")

        frame = ttk.LabelFrame(tab, text="文件选择与净化", padding=10)
        frame.pack(fill="x", padx=10, pady=10)
        ttk.Button(frame, text="选择 TXT", command=self.select_txt).grid(row=0, column=0, padx=5)
        ttk.Entry(frame, textvariable=self.txt_path, width=50, state="readonly").grid(row=0, column=1, padx=5)
        self.btn_convert = ttk.Button(frame, text="净化并转换为 EPUB", command=self.start_convert)
        self.btn_convert.grid(row=0, column=2, padx=5)

        self.clean_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(frame, text="启用广告净化与智能排版", variable=self.clean_var).grid(row=1, column=1, sticky="w", pady=5)

    # ================= 4. 书源管理标签页 =================
    def _build_sources_tab(self):
        tab = ttk.Frame(self.notebook)
        self.notebook.add(tab, text="⚙️ 书源管理")

        cols = ("name", "url", "status")
        self.tree_sources = ttk.Treeview(tab, columns=cols, show="headings", height=10)
        self.tree_sources.heading("name", text="书源名称")
        self.tree_sources.heading("url", text="URL")
        self.tree_sources.heading("status", text="状态")
        self.tree_sources.pack(fill="both", expand=True, padx=10, pady=10)
        
        self._refresh_sources_tree()

        btn_frame = ttk.Frame(tab)
        btn_frame.pack(fill="x", padx=10, pady=5)
        ttk.Button(btn_frame, text="一键测速 (模拟)", command=self.test_sources).pack(side="left", padx=5)
        ttk.Button(btn_frame, text="清理失效源", command=self.clean_sources).pack(side="left", padx=5)
        ttk.Button(btn_frame, text="保存配置", command=self.save_config).pack(side="right", padx=5)

    def _refresh_sources_tree(self):
        for item in self.tree_sources.get_children():
            self.tree_sources.delete(item)
        for src in self.sources:
            tag = "ok" if src["status"] == "正常" else "fail"
            self.tree_sources.insert("", tk.END, values=(src["name"], src["url"], src["status"]), tags=(tag,))
        self.tree_sources.tag_configure("fail", foreground="red")
        self.tree_sources.tag_configure("ok", foreground="green")

    def test_sources(self):
        self.log("🚀 开始模拟测速...")
        for src in self.sources:
            src["status"] = "正常" if "test" not in src["url"] else "失效"
        self._refresh_sources_tree()
        self.log("✅ 测速完成！")

    def clean_sources(self):
        self.sources = [s for s in self.sources if s["status"] == "正常"]
        self._refresh_sources_tree()
        self.log("🧹 已清理失效书源！")

    def save_config(self):
        self.config["save_path"] = self.save_path.get()
        self.config["max_workers"] = self.max_workers.get()
        self.config["sources"] = self.sources
        ConfigManager.save(self.config)
        self.log("💾 配置已保存！")

    # ================= 核心通用逻辑 =================
    def _build_status_bar(self):
        self.status_bar = ttk.Label(self.root, text=f"就绪 | 核心程序: {os.path.basename(self.exe_path)}", relief="sunken", anchor="w")
        self.status_bar.pack(side="bottom", fill="x")

    def log(self, message):
        def _log():
            self.text_log.config(state="normal")
            self.text_log.insert(tk.END, f"> {message}\n")
            self.text_log.see(tk.END)
            self.text_log.config(state="disabled")
        self.root.after(0, _log)

    def set_running(self, state):
        self.is_running = state
        state_str = "normal" if not state else "disabled"
        self.root.after(0, lambda: self.btn_download.config(state=state_str))
        self.root.after(0, lambda: self.btn_convert.config(state=state_str))
        status_text = "正在运行..." if state else "就绪"
        self.root.after(0, lambda: self.status_bar.config(text=f"{status_text} | 核心程序: {os.path.basename(self.exe_path)}"))

    # --- 下载与批量逻辑 ---
    def start_download(self):
        keyword = self.entry_search.get().strip()
        if not keyword: return messagebox.showwarning("提示", "请输入关键词！")
        if not os.path.exists(self.exe_path): return messagebox.showerror("错误", f"未找到核心程序: {self.exe_path}")
        
        min_words = self.entry_min_words.get().strip()
        self.log(f"🚀 开始搜索: {keyword} (过滤字数: {min_words})")
        self.set_running(True)
        threading.Thread(target=self._download_task, args=(keyword,), daemon=True).start()

    def _download_task(self, keyword):
        try:
            os.makedirs(self.save_path.get(), exist_ok=True)
            cmd = [self.exe_path, "-s", keyword, "-o", self.save_path.get()]
            self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, encoding='utf-8', errors='ignore')
            for line in self.process.stdout:
                if self.stop_event.is_set(): self.process.terminate(); break
                self.log(line.strip())
            self.process.wait()
            self.log(f"✅ 下载完成: {keyword}")
        except Exception as e: self.log(f"❌ 出错: {str(e)}")
        finally: self.set_running(False)

    def add_to_queue(self):
        content = self.batch_text.get("1.0", tk.END).strip()
        if not content: return
        lines = [l.strip() for l in content.split('\n') if l.strip()]
        for line in lines: self.task_queue.put(line)
        self.batch_text.delete("1.0", tk.END)
        self.log(f"📥 已加入 {len(lines)} 个任务，当前队列: {self.task_queue.qsize()}")

    def start_batch(self):
        if self.task_queue.empty(): return messagebox.showwarning("提示", "队列为空！")
        if self.is_running: return messagebox.showwarning("提示", "已有任务运行中！")
        self.stop_event.clear()
        self.set_running(True)
        threading.Thread(target=self._batch_worker, daemon=True).start()

    def _batch_worker(self):
        with ThreadPoolExecutor(max_workers=self.max_workers.get()) as executor:
            futures = []
            while not self.task_queue.empty() and not self.stop_event.is_set():
                futures.append(executor.submit(self._single_batch_task, self.task_queue.get()))
            for f in as_completed(futures): f.result()
        self.log("🎉 批量任务全部完成！")
        self.set_running(False)

    def _single_batch_task(self, keyword):
        if self.stop_event.is_set(): return
        try:
            cmd = [self.exe_path, "-s", keyword, "-o", self.save_path.get()]
            self.log(f"  ├─ 开始: {keyword}")
            p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, encoding='utf-8', errors='ignore')
            for line in p.stdout:
                if self.stop_event.is_set(): p.terminate(); break
            p.wait()
            self.log(f"  └─ ✅ 完成: {keyword}")
        except Exception as e: self.log(f"  └─ ❌ 失败: {keyword}")

    def stop_all(self):
        self.stop_event.set()
        while not self.task_queue.empty():
            try: self.task_queue.get_nowait()
            except: break
        self.log("🛑 已停止所有任务并清空队列")

    # --- TXT 净化与转换逻辑 ---
    def select_txt(self):
        path = filedialog.askopenfilename(filetypes=[("Text Files", "*.txt")])
        if path: self.txt_path.set(path)

    def start_convert(self):
        path = self.txt_path.get()
        if not path: return messagebox.showwarning("提示", "请选择 TXT！")
        self.set_running(True)
        threading.Thread(target=self._convert_task, args=(path,), daemon=True).start()

    def _convert_task(self, txt_path):
        try:
            with open(txt_path, 'r', encoding='utf-8') as f: content = f.read()
            
            # 【新增】广告净化
            if self.clean_var.get():
                ad_patterns = [r'手机阅读.*', r'最新网址.*', r'请收藏.*']
                for p in ad_patterns: content = re.sub(p, '', content)
                self.log("🧹 广告净化完成！")

            chapters = re.split(r'(第[一二三四五六七八九十百千万\d]+[章回].*)', content)
            book = epub.EpubBook()
            book.set_title(os.path.basename(txt_path).replace('.txt', ''))
            book.set_language('zh')
            
            css = epub.EpubItem(uid="style", file_name="style/default.css", media_type="text/css", 
                                content="p { text-indent: 2em; line-height: 1.8; margin-bottom: 0.5em; } h1 { text-align: center; margin-bottom: 1em; }")
            book.add_item(css)
            spine = ['nav']

            if len(chapters) > 1:
                for i in range(1, len(chapters), 2):
                    title = chapters[i].strip()
                    text = chapters[i+1] if i+1 < len(chapters) else ""
                    html = f"<h1>{title}</h1>{''.join(f'<p>{line}</p>' for line in text.split('\n') if line.strip())}"
                    c = epub.EpubHtml(title=title, file_name=f'chap_{i}.xhtml', lang='zh')
                    c.content = html; c.add_item(css); book.add_item(c); spine.append(c)
            else:
                html = f"<h1>{book.title}</h1>{''.join(f'<p>{line}</p>' for line in content.split('\n') if line.strip())}"
                c = epub.EpubHtml(title='正文', file_name='text.xhtml', lang='zh')
                c.content = html; c.add_item(css); book.add_item(c); spine.append(c)

            book.spine = spine
            book.add_item(epub.EpubNcx()); book.add_item(epub.EpubNav())
            
            save_path = txt_path.replace('.txt', '.epub')
            epub.write_epub(save_path, book, {})
            self.log(f"✅ 转换成功！保存至: {save_path}")
        except Exception as e: self.log(f"❌ 转换出错: {str(e)}")
        finally: self.set_running(False)

    def select_save_path(self):
        path = filedialog.askdirectory()
        if path: self.save_path.set(path)

if __name__ == "__main__":
    root = tk.Tk()
    app = SoNovelUltimateGUI(root)
    root.mainloop()