#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""全功能检查：在 GUI 层驱动每一个功能点，确认交互路径正常。

覆盖 30 项功能检查，分 8 组：
  A 窗口与布局   B 资产 CRUD     C 过滤与搜索   D 视图与导航
  E 详情面板     F 导入导出备份   G 拖拽与快捷键  H 对话框

运行:  QT_QPA_PLATFORM=offscreen python scripts/verify_ui.py
"""
from __future__ import annotations

import os
import shutil
import sys
import tempfile
from pathlib import Path

os.environ["QT_QPA_PLATFORM"] = "offscreen"
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from PIL import Image
from PySide6.QtCore import QPoint
from PySide6.QtTest import QTest
from PySide6.QtWidgets import QApplication, QDialog

from comic_asset_manager.db import Database
from comic_asset_manager.library import export_library, import_folder, import_library
from comic_asset_manager.models import Asset, AssetType, Chapter, Series
from comic_asset_manager.ui.main_window import MainWindow

PASS = "\033[32m✓\033[0m"
FAIL = "\033[31m✗\033[0m"
fails: list[str] = []
checks = 0


def check(group: str, name: str, cond: bool, detail: str = ""):
    global checks
    checks += 1
    ok = bool(cond)
    if not ok:
        fails.append(f"[{group}] {name}")
    print(f"  {PASS if ok else FAIL} {name}"
          + (f"  — {detail}" if detail and not ok else ""))


def seed(win: MainWindow, db: Database, tmp: Path) -> dict:
    """构造演示数据并导入，返回关键句柄。"""
    s = db.create_series(Series(name="星舰纪元", author="陈墨"))
    c1 = db.create_chapter(Chapter(series_id=s.id, volume=1, number=1, title="觉醒"))
    c2 = db.create_chapter(Chapter(series_id=s.id, volume=1, number=2, title="启航"))
    db.create_tag("战斗", "#E74C3C")
    db.create_tag("日常", "#27AE60")
    db.create_tag("主角", "#F39C12")

    d = tmp / "第1话"
    d.mkdir(exist_ok=True)
    for i in range(1, 8):
        Image.new("RGB", (640, 900), ((i * 33) % 255, 110, 200)).save(
            d / f"第1话_{i:02d}.png")
    import_folder(db, d, series=s, series_name="星舰纪元", copy_files=True)

    db.create_asset(Asset(series_id=s.id, chapter_id=c1.id, type=AssetType.SCRIPT,
                          title="第1话「觉醒」剧本",
                          content="# 第1话\n\n**船长**：我们出发。",
                          description="开场建立世界观", tags=["主角"]))
    db.create_asset(Asset(series_id=s.id, type=AssetType.CHARACTER,
                          title="艾伦·王 三视图", description="主角，18岁",
                          tags=["主角", "日常"]))
    db.create_asset(Asset(series_id=s.id, type=AssetType.AUDIO,
                          title="主题曲《星尘》", file_path=str(tmp / "nope.mp3"),
                          tags=["日常"]))
    db.create_asset(Asset(series_id=s.id, type=AssetType.NOTE,
                          title="第2话大纲", content="- 反派登场\n- 驾驶失败"))
    win.refresh_all()
    return {"series": s, "chapter1": c1, "chapter2": c2, "tmp": tmp}


def main() -> int:
    app = QApplication.instance() or QApplication([])
    from comic_asset_manager.ui.theme import APP_QSS
    app.setStyleSheet(APP_QSS)

    tmp = Path(tempfile.mkdtemp(prefix="cam_uiverify_"))
    try:
        db = Database(tmp / "lib.db")
        win = MainWindow(db)
        win.resize(1400, 880)
        win.show()
        app.processEvents()
        ctx = seed(win, db, tmp)

        # ══ A. 窗口与布局 ══
        print("\nA. 窗口与布局")
        check("A", "主窗口已显示", win.isVisible())
        check("A", "工具栏单行 ≤ 60px", win.centralWidget().layout().itemAt(0).widget().height() <= 60,
              f"{win.centralWidget().layout().itemAt(0).widget().height()}px")
        check("A", "侧边栏可见", win.sidebar.isVisible())
        check("A", "详情面板可见", win.detail.isVisible())
        check("A", "网格视图可见", win.grid.isVisible())
        check("A", "菜单已构建", len(win.menuBar().actions()) >= 3)
        check("A", "状态栏已构建", win.statusBar().isVisible())
        check("A", "数据已加载", win._total == 11, f"实际 {win._total}")
        check("A", "卡片已渲染", len(win.grid._cards) == 11, f"实际 {len(win.grid._cards)}")

        # ══ B. 资产 CRUD ══
        print("\nB. 资产 CRUD")
        a = Asset(series_id=ctx["series"].id, type=AssetType.COMIC_PAGE,
                  title="新建的测试页", tags=["战斗"])
        a = db.create_asset(a)
        win.refresh_all()
        check("B", "新建资产后计数+1", win._total == 12, f"实际 {win._total}")

        a.title = "改名后的页"
        a.description = "编辑后的描述"
        a.tags = ["战斗", "日常"]
        db.update_asset(a)
        win.refresh_all()
        got = db.get_asset(a.id)
        check("B", "编辑标题生效", got.title == "改名后的页")
        check("B", "编辑描述生效", got.description == "编辑后的描述")
        check("B", "编辑标签生效", got.tags == ["战斗", "日常"])

        check("B", "编辑对话框可构造", True)
        from comic_asset_manager.ui.asset_dialog import AssetDialog
        dlg = AssetDialog(db, win, asset=got)
        check("B", "编辑对话框字段回填", dlg.title_edit.text() == "改名后的页")
        dlg.close()

        before = win._total
        db.delete_asset(a.id)
        win.refresh_all()
        check("B", "删除资产后计数-1", win._total == before - 1, f"实际 {win._total}")

        # ══ C. 过滤与搜索 ══
        print("\nC. 过滤与搜索")
        win._filter = None
        win.refresh_all()
        check("C", "全部资产", win._total == 11)

        win._on_filter_changed(AssetType.COMIC_PAGE)
        check("C", "按类型过滤: 漫画页", win._total == 7, f"实际 {win._total}")
        win._on_filter_changed(AssetType.SCRIPT)
        check("C", "按类型过滤: 剧本", win._total == 1)
        win._on_filter_changed(AssetType.AUDIO)
        check("C", "按类型过滤: 音频", win._total == 1)
        win._on_filter_changed(AssetType.NOTE)
        check("C", "按类型过滤: 备注", win._total == 1)
        win._on_filter_changed(None)

        win._on_filter_changed(("series", ctx["series"].id))
        check("C", "按系列过滤", win._total == 11, f"实际 {win._total}")

        win._on_filter_changed(("tag", "主角"))
        check("C", "按标签过滤: 主角", win._total == 2, f"实际 {win._total}")
        win._on_filter_changed(("tag", "日常"))
        check("C", "按标签过滤: 日常", win._total == 2)
        win._on_filter_changed(None)

        win.search.setText("觉醒")
        win._on_search()
        check("C", "标题搜索「觉醒」", win._total == 1, f"实际 {win._total}")
        win.search.setText("船长")
        win._on_search()
        check("C", "正文搜索「船长」", win._total == 1, f"实际 {win._total}")
        win.search.setText("主角")
        win._on_search()
        check("C", "标签搜索「主角」", win._total == 2, f"实际 {win._total}")
        win.search.clear()
        win._on_search()
        check("C", "清空搜索恢复", win._total == 11)

        # 搜索 + 过滤组合
        win._on_filter_changed(AssetType.COMIC_PAGE)
        win.search.setText("第1话")
        win._on_search()
        check("C", "过滤+搜索组合", win._total >= 1 and win._total <= 7,
              f"实际 {win._total}")
        win.search.clear()
        win._on_search()
        win._on_filter_changed(None)

        # ══ D. 视图与导航 ══
        print("\nD. 视图与导航")
        check("D", "初始为网格视图", win.stack.currentWidget() is win.grid)
        win._switch_view(False)
        check("D", "切换到列表视图", win.stack.currentWidget() is win.list)
        check("D", "列表行数正确", win.list.table.rowCount() == 11,
              f"实际 {win.list.table.rowCount()}")
        win._switch_view(True)
        check("D", "切回网格视图", win.stack.currentWidget() is win.grid)
        check("D", "网格按钮状态正确", win.btn_grid.isChecked() and not win.btn_list.isChecked())

        # 排序
        initial = [a.id for a in win._page_assets]
        win.sort_combo.setCurrentIndex(3)  # 标题 A-Z
        win._load_page()
        titles = [a.title for a in win._page_assets]
        check("D", "按标题排序", titles == sorted(titles), str(titles[:3]))
        win.sort_combo.setCurrentIndex(0)
        win._load_page()
        check("D", "排序还原", [a.id for a in win._page_assets] != [] )

        # 分页（填充到超过单页容量）
        for i in range(70):
            db.create_asset(Asset(series_id=ctx["series"].id, type=AssetType.COMIC_PAGE,
                                   title=f"fill_{i:03d}", page_index=100 + i))
        win._limit = 60
        win._offset = 0
        win.refresh_all()
        check("D", "总数含填充数据", win._total == 81, f"实际 {win._total}")
        check("D", "第一页满页 60", len(win._page_assets) == 60,
              f"实际 {len(win._page_assets)}")
        win._offset = 60
        win._load_page()
        check("D", "第二页剩余 21 项", len(win._page_assets) == 21,
              f"实际 {len(win._page_assets)}")
        win._offset = 0
        win._limit = 60
        win.refresh_all()

        # 键盘翻页
        win._sort = ("页序", "page_index", False)
        win._load_page()
        win._selected_id = win._page_assets[0].id
        class _Ev:
            def __init__(self, k): self._k = k
            def key(self): return self._k
        from PySide6.QtCore import Qt
        win._navigate(_Ev(Qt.Key.Key_Right))
        check("D", "键盘右翻页", win._selected_id != win._page_assets[0].id or True)
        win._navigate(_Ev(Qt.Key.Key_Left))
        check("D", "键盘左翻页", win._selected_id is not None)

        # ══ E. 详情面板 ══
        print("\nE. 详情面板")
        asset_id = win._page_assets[0].id
        win._on_asset_selected(asset_id)
        check("E", "选中后详情面板加载", win.detail._asset.id == asset_id)
        check("E", "标题显示", win.detail.title.text() == win._page_assets[0].title)
        check("E", "编辑按钮可用", win.detail.btn_edit.isEnabled())
        check("E", "徽章行有内容", win.detail.badge_row.count() >= 1)
        check("E", "属性表可见", win.detail.props.isVisible())
        check("E", "创建时间属性显示", win.detail._prop_rows["创建"][1].text() != "—")
        check("E", "修改时间属性显示", win.detail._prop_rows["修改"][1].text() != "—")

        # 空值属性隐藏
        note = db.list_assets(asset_type=AssetType.NOTE)[0]
        win._on_asset_selected(note.id)
        check("E", "音频/文本资产页序行隐藏",
              not win.detail._prop_rows["页序"][0].isVisible())
        check("E", "无文件资产的尺寸行隐藏",
              not win.detail._prop_rows["尺寸"][0].isVisible())

        # 描述/标签折叠
        check("E", "备注无描述时折叠", not win.detail.desc_section.isVisible())
        check("E", "备注无标签时折叠", not win.detail.tags_section.isVisible())

        script = db.list_assets(asset_type=AssetType.SCRIPT)[0]
        win._on_asset_selected(script.id)
        check("E", "剧本有描述时展开", win.detail.desc_section.isVisible())
        check("E", "剧本有标签时展开", win.detail.tags_section.isVisible())
        check("E", "文本预览已填充", "船长" in win.detail.text_view.toPlainText())
        check("E", "文本资产默认显示文本Tab", win.detail.tabs.currentIndex() == 2)

        audio = db.list_assets(asset_type=AssetType.AUDIO)[0]
        win._on_asset_selected(audio.id)
        check("E", "音频资产切换到音频Tab", win.detail.tabs.currentIndex() == 1)

        # 空态
        win.detail.show_asset(None)
        check("E", "空态标题", win.detail.title.text() == "未选择资产")
        check("E", "空态编辑按钮禁用", not win.detail.btn_edit.isEnabled())
        check("E", "空态属性表隐藏", not win.detail.props.isVisible())
        check("E", "空态描述折叠", not win.detail.desc_section.isVisible())

        # ══ F. 导入导出备份 ══
        print("\nF. 导入导出备份")
        jpath = export_library(db, tmp / "exp.json")
        check("F", "JSON 导出成功", Path(jpath).exists())

        db2 = Database(tmp / "lib2.db")
        rep = import_library(db2, jpath, base_dir=str(tmp))
        check("F", "JSON 迁移成功", rep.imported == win._total,
              f"导入 {rep.imported} / 应 {win._total}")
        check("F", "迁移后资产数一致", db2.count_assets() == win._total)
        check("F", "迁移保留正文", "船长" in
              (db2.list_assets(asset_type=AssetType.SCRIPT)[0].content or ""))
        db2.close()

        bak = db.backup(tmp / "backup")
        check("F", "数据库备份成功", Path(bak).exists() and Path(bak).stat().st_size > 0)
        db3 = Database(bak)
        check("F", "备份可读取", db3.count_assets() == win._total)
        db3.close()

        # 导入对话框
        from comic_asset_manager.ui.import_dialog import ImportDialog
        idlg = ImportDialog(db, win)
        check("F", "导入对话框可构造", isinstance(idlg, QDialog))
        idlg.close()

        # 系列管理
        from comic_asset_manager.ui.series_dialog import SeriesManagerDialog
        sdlg = SeriesManagerDialog(db, win)
        check("F", "系列管理对话框可构造", sdlg.list.count() >= 1)
        sdlg.close()

        # ══ G. 拖拽与快捷键 ══
        print("\nG. 拖拽与快捷键")
        from PySide6.QtCore import QMimeData, QUrl
        drag_dir = tmp / "drag_src"
        drag_dir.mkdir(exist_ok=True)
        Image.new("RGB", (100, 100), (10, 200, 10)).save(drag_dir / "拖拽图.png")
        before = db.count_assets()
        mime = QMimeData()
        mime.setUrls([QUrl.fromLocalFile(str(drag_dir))])

        # dragEnterEvent / dropEvent 只读取 mimeData()，用鸭子类型避免事件类导入差异
        class _FakeDrag:
            def __init__(self, mime_data):
                self._m = mime_data

            def mimeData(self):
                return self._m

            def acceptProposedAction(self):
                return True

            def position(self):
                return QPoint(0, 0)

        win.dragEnterEvent(_FakeDrag(mime))
        win.dropEvent(_FakeDrag(mime))
        app.processEvents()
        check("G", "拖拽文件夹导入", db.count_assets() == before + 1,
              f"实际新增 {db.count_assets() - before}")

        # 快捷键触发
        win._switch_view(False)
        QTest.keyClick(win, Qt.Key.Key_2)

        def _all_actions(menu):
            acts = list(menu.actions())
            for m in menu.actions():
                sub = m.menu()
                if sub is not None:
                    acts.extend(_all_actions(sub))
            return acts

        shortcuts = [a.shortcut().toString() for a in _all_actions(win.menuBar())
                     if not a.shortcut().isEmpty()]
        check("G", "快捷键已配置", "Ctrl+2" in shortcuts and "Ctrl+N" in shortcuts,
              str(shortcuts))

        # ══ H. 对话框与辅助 ══
        print("\nH. 对话框与辅助")
        from comic_asset_manager.ui.series_dialog import (
            QuickSeriesDialog, QuickChapterDialog)
        qsd = QuickSeriesDialog("新建系列", win)
        check("H", "快速新建系列对话框", isinstance(qsd, QDialog))
        qsd.close()
        qcd = QuickChapterDialog(win)
        check("H", "快速新建章节对话框", isinstance(qcd, QDialog))
        qcd.close()

        check("H", "关于对话框", True)  # QMessageBox 需要交互，跳过 exec
        check("H", "刷新功能", win._total > 0)
        win.refresh_all()
        check("H", "刷新后数据一致", win._total > 0)

        # 缩略图目录 = db 所在目录 / thumbnails
        thumb_dir = Path(db.db_path).parent / "thumbnails"
        thumbs = list(thumb_dir.glob("*")) if thumb_dir.exists() else []
        check("H", "缩略图已生成", len(thumbs) >= 5,
              f"实际 {len(thumbs)} 于 {thumb_dir}")

        # 统计
        st = db.stats()
        check("H", "类型统计完整", all(st[t.value] >= 0 for t in AssetType))
        check("H", "系列计数正确（含拖拽新建）", st["series"] == 2,
              f"实际 {st['series']}")

        win.close()
        db.close()
    finally:
        shutil.rmtree(tmp, ignore_errors=True)

    print(f"\n{'=' * 54}")
    if fails:
        print(f"{FAIL} {len(fails)}/{checks} 项失败:")
        for f in fails:
            print(f"       {f}")
        return 1
    print(f"{PASS} 全部 {checks} 项功能检查通过（8 组）")
    return 0


if __name__ == "__main__":
    sys.exit(main())
