"""CountThread 模块 - 文件计数线程 - 支持多线程并行处理."""

from __future__ import annotations

import os
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime, timezone
from pathlib import Path
from typing import TYPE_CHECKING

from PySide2.QtCore import QThread, Signal

if TYPE_CHECKING:
    from ..gui import FileScanner

from ..models.config import conf
from ..models.fileinfo import FileInfo
from ..models.logger import logger

_MIN_SHOW_IGNORED_DIRS = 100


class CountThread(QThread):
    """采用多线程并行搜索当前目录下对应的文件."""

    signal_finish_count = Signal(object, Path)  # 文件信息准备好信号 (用于数据库保存,类型为 list[DirFileInfo])
    signal_update_file_count = Signal(int)  # 文件计数更新信号
    signal_msg = Signal(str)  # 消息通知信号
    signal_status = Signal(str)  # 状态通知信号
    signal_current_directory = Signal(str)  # 当前处理目录信号
    signal_progress_update = Signal(int, int)  # 进度更新信号 (百分比,总数 100)

    def __init__(self, parent: FileScanner, directory: Path) -> None:
        super().__init__(parent)

        self._parent: FileScanner = parent
        self._directory: Path = directory
        self._files_info: list[FileInfo] = []
        self._sub_dirs: list[Path] = [Path(directory)]
        self._flag_quit: bool = False

        # 连接信号到槽函数
        self.signal_update_file_count.connect(self._parent.on_update_filecount)
        self.signal_finish_count.connect(self._parent.on_finish_count)
        self.signal_finish_count.connect(self._parent.on_save_directory_files_db)
        self.signal_msg.connect(self._parent.on_update_msg)
        self.signal_status.connect(self._parent.on_update_status)
        self.signal_current_directory.connect(self._parent.on_update_current_directory)
        self.signal_progress_update.connect(self._parent.on_update_progress)

    def run(self) -> None:
        """搜索当前目录下的文件 - 使用多线程并行处理."""
        self.signal_status.emit("正在统计文件数量...")
        self.signal_msg.emit(f"启动多线程计数 [线程数:{conf.MAX_WORKERS}]")

        # 获取忽略的目录和文件类型配置
        ignore_file_types = tuple(conf.IGNORE_FILE_TYPES)

        try:
            root_path = Path(self._directory)
            if not root_path.exists():
                self.signal_msg.emit(f"目录不存在:{self._directory}")
                self.signal_finish_count.emit([], self._directory)
                return

            # 第一阶段:收集所有目录结构
            ignored_count = self._collect_directories(root_path, set(conf.IGNORE_DIRS))
            self.signal_msg.emit(f"发现 {len(self._sub_dirs)} 个目录,忽略 {ignored_count} 个目录")

            # 第二阶段:统计文件
            self.signal_msg.emit(f"开始并行统计 {len(self._sub_dirs)} 个目录中的文件...")
            file_count = self._parallel_count(ignore_file_types)

            self.signal_msg.emit(f"更新目录完成 - 共 {file_count} 个文件")

        except (OSError, RuntimeError) as e:
            self.signal_msg.emit(f"计数过程出错:{e}")
            self.signal_finish_count.emit([], self._directory)

    def _collect_directories(self, root_path: Path, ignore_dirs: set[str]) -> int:
        """收集所有目录结构并统计忽略的目录数."""
        # 使用动态估计值计算进度(因为无法预知总目录数)
        ignored_count = 0
        estimated_total = 1000

        for dir_counter, (current_root, dirs, _files) in enumerate(os.walk(root_path), start=1):
            if self._flag_quit:
                self.signal_msg.emit("用户取消计数")
                break

            # 检查并移除被忽略的目录
            ignored_dirs_in_walk = [
                d for d in dirs if self._should_ignore_directory(Path(current_root) / d, ignore_dirs)
            ]

            for d in ignored_dirs_in_walk:
                dirs.remove(d)
                ignored_count += 1

                # 实时显示前 100 个忽略目录
                if ignored_count <= _MIN_SHOW_IGNORED_DIRS:
                    self.signal_msg.emit(f"⚠️  忽略目录:{Path(current_root) / d}")

            # 收集有效子目录
            self._sub_dirs.extend([Path(current_root) / d for d in dirs])

            # 动态调整估计值并更新进度
            if dir_counter > estimated_total // 2:
                estimated_total = dir_counter * 2

            # 节流:每 100 个目录更新一次进度
            if dir_counter % 100 == 0:
                self.signal_current_directory.emit(str(current_root))
                progress = min(29, int((dir_counter / estimated_total) * 100))  # 第一阶段最多到 29%
                self.signal_progress_update.emit(progress, 100)

        # 显示忽略目录汇总
        if ignored_count > _MIN_SHOW_IGNORED_DIRS:
            self.signal_msg.emit(f"⚠️  ... 还有 {ignored_count - _MIN_SHOW_IGNORED_DIRS} 个目录被忽略")

        # 第一阶段完成,固定在 30%
        self.signal_progress_update.emit(30, 100)
        return ignored_count

    def _parallel_count(self, ignore_file_types: tuple[str, ...]) -> int:
        """并行统计多个目录中的文件数."""
        total_count = 0
        batch_size = max(1, len(self._sub_dirs) // conf.MAX_WORKERS)
        completed_batches = 0
        total_dirs = len(self._sub_dirs)

        # 优化:计算进度更新频率 - 确保最多 50 次更新,避免信号过于频繁
        total_batches = (total_dirs + batch_size - 1) // batch_size
        progress_interval = max(1, total_batches // 50)  # 至少每 50 批更新一次

        with ThreadPoolExecutor(max_workers=conf.MAX_WORKERS) as executor:
            futures = {}
            for i in range(0, len(self._sub_dirs), batch_size):
                batch = self._sub_dirs[i : i + batch_size]
                future = executor.submit(self._count_directory_batch_with_info, batch, ignore_file_types)
                futures[future] = (i, min(i + batch_size, len(self._sub_dirs)))

            # 收集结果
            for future in as_completed(futures):
                if self._flag_quit:
                    break
                start, end = futures[future]
                try:
                    count, files_info = future.result()
                    total_count += count
                    self._files_info.extend(files_info)
                    completed_batches += 1

                    # 节流:根据总批次数动态调整更新频率,确保进度条能持续更新
                    if completed_batches % progress_interval == 0:
                        self.signal_update_file_count.emit(total_count)
                        # 进度 = 30% + (已处理数/总数) × 70%
                        progress = 30 + int((end / total_dirs) * 70)
                        self.signal_progress_update.emit(min(99, progress), 100)

                        # 同时更新当前目录信息(使用当前批次的起始目录)
                        batch_index = min(start, len(self._sub_dirs) - 1)
                        self.signal_current_directory.emit(str(self._sub_dirs[batch_index]))

                except (OSError, RuntimeError) as e:
                    self.signal_msg.emit(f"批次 {start}-{end} 计数失败:{e}")

        # 完成时显示 100%
        self.signal_progress_update.emit(100, 100)
        self.signal_finish_count.emit(self._files_info, self._directory)

        return total_count

    def _should_ignore_directory(self, directory: Path, ignore_dirs: set[str]) -> bool:
        """检查是否应该忽略指定目录.

        支持两种忽略模式:
        1. 完整路径匹配:如 "C:/Windows" - 跳过该完整路径及其子目录
        2. 文件夹名匹配:如 ".venv", ".git", "node_modules" - 跳过路径中任何包含该名称的目录及其子目录

        Args:
            directory: 目录路径 (可以是完整路径或相对路径)
            ignore_dirs: 忽略的目录集合

        Returns
        -------
            如果应该忽略则返回 True,否则返回 False
        """
        # 统一使用正斜杠并转为小写进行比较
        # 注意:Path.as_posix() 可以正确处理路径分隔符转换
        dir_str = directory.as_posix().lower()

        for ignore_dir in ignore_dirs:
            # 标准化 ignore_dir 的路径分隔符和大小写
            ignore_dir_normalized = Path(ignore_dir).as_posix().lower()

            # 检查是否包含路径分隔符
            has_path_sep = "/" in ignore_dir_normalized

            if has_path_sep:
                # 模式 1: 完整路径匹配 - 精确匹配或以该路径开头
                # 例如:"C:/Windows" 会匹配 "C:/Windows" 和 "C:/Windows/System32"
                if dir_str == ignore_dir_normalized or dir_str.startswith(ignore_dir_normalized + "/"):
                    return True

            # 模式 2: 文件夹名匹配 - 只要路径中包含该文件夹名就匹配
            # 例如:".venv" 会匹配 "D:/project/.venv" 和 "D:/project/.venv/Lib"
            # 使用 / 包围来确保匹配的是完整的文件夹名,而不是部分匹配
            elif (
                f"/{ignore_dir_normalized}" in dir_str
                or dir_str.startswith(ignore_dir_normalized + "/")
                or dir_str.endswith(f"/{ignore_dir_normalized}")
            ):
                return True

        return False

    def _should_ignore_file(self, filename: str, ignore_file_types: tuple[str, ...]) -> bool:
        """检查是否应该忽略指定文件.

        Args:
            filename: 文件名
            ignore_file_types: 忽略的文件类型元组

        Returns
        -------
            如果应该忽略则返回 True,否则返回 False
        """
        return filename.lower().endswith(ignore_file_types)

    def _count_directory_batch_with_info(
        self,
        directories: list[Path],
        ignore_file_types: tuple[str, ...],
    ) -> tuple[int, list[FileInfo]]:
        """统计一批目录中的文件数并收集文件信息.

        注意:目录列表已在前置阶段过滤,此方法只统计当前层的文件,不递归.

        Args:
            directories: 目录列表(已过滤掉被忽略的目录)
            ignore_file_types: 忽略的文件类型元组

        Returns
        -------
            (文件数量,文件信息列表) 元组
        """
        count = 0
        files_info: list[FileInfo] = []
        for directory in directories:
            if self._flag_quit:
                break

            try:
                if not directory.exists() or not directory.is_dir():
                    continue

                # 只统计当前目录的文件(不递归,因为 all_dirs 已包含所有子目录)
                for item in directory.iterdir():
                    if self._flag_quit:
                        break

                    if item.is_file() and not self._should_ignore_file(item.name, ignore_file_types):
                        count += 1
                        # 收集文件信息为 DirFileInfo 对象
                        try:
                            stat_info = item.stat()
                            files_info.append(
                                FileInfo(
                                    id=0,
                                    root_path=str(directory),
                                    file_path=str(item),
                                    file_type=item.suffix.lower() if item.suffix else "",
                                    file_size=stat_info.st_size,
                                    creation_time=datetime.fromtimestamp(
                                        stat_info.st_ctime,
                                        tz=timezone.utc,
                                    ).isoformat(),
                                    modification_time=datetime.fromtimestamp(
                                        stat_info.st_mtime,
                                        tz=timezone.utc,
                                    ).isoformat(),
                                    is_skipped=0,
                                    skip_reason=None,
                                    scan_status="pending",
                                    metadata="{}",
                                ),
                            )
                        except (OSError, ValueError):
                            logger.warning(f"无法获取文件统计信息:{item}")
                            return 0, []

            except (PermissionError, OSError):
                continue

        return count, files_info
