"""热插拔插件 - 运行时加载/卸载/更新插件"""
import sys
import time
import threading
from pathlib import Path
from typing import Any, Optional, Callable

from oss.logger.logger import Log
from oss.plugin.types import Plugin, register_plugin_type


class HotReloadError(Exception):
    """热插拔错误"""
    pass


class FileWatcher:
    """文件监听器"""

    def __init__(self, watch_dirs: list[str], extensions: list[str], on_change: Callable):
        self.watch_dirs = [Path(d) for d in watch_dirs]
        self.extensions = extensions
        self.on_change = on_change
        self._running = False
        self._thread: Optional[threading.Thread] = None
        self._file_times: dict[str, float] = {}
        self._scan_files()

    def _scan_files(self):
        """扫描当前文件及其修改时间"""
        for watch_dir in self.watch_dirs:
            if watch_dir.exists():
                for f in watch_dir.rglob("*"):
                    if f.is_file() and f.suffix in self.extensions:
                        self._file_times[str(f)] = f.stat().st_mtime

    def start(self):
        """开始监听"""
        self._running = True
        self._thread = threading.Thread(target=self._watch_loop, daemon=True)
        self._thread.start()

    def stop(self):
        """停止监听"""
        self._running = False
        if self._thread:
            self._thread.join(timeout=5)

    def _watch_loop(self):
        """监听循环"""
        while self._running:
            changed = []
            current_files = {}

            for watch_dir in self.watch_dirs:
                if watch_dir.exists():
                    for f in watch_dir.rglob("*"):
                        if f.is_file() and f.suffix in self.extensions:
                            fpath = str(f)
                            mtime = f.stat().st_mtime
                            current_files[fpath] = mtime

                            # 新文件或修改过
                            if fpath not in self._file_times:
                                changed.append(("new", f))
                            elif mtime > self._file_times[fpath]:
                                changed.append(("modified", f))

            # 检查删除的文件
            for fpath in self._file_times:
                if fpath not in current_files:
                    changed.append(("deleted", Path(fpath)))

            if changed:
                self._file_times = current_files
                self.on_change(changed)

            time.sleep(1)


class HotReloadPlugin(Plugin):
    """热插拔插件"""

    def __init__(self):
        self.plugin_loader_instance = None
        self.watcher: Optional[FileWatcher] = None
        self.watch_dirs: list[str] = []
        self.watch_extensions: list[str] = [".py", ".json"]

    def init(self, deps: dict = None):
        """初始化"""
        pass

    def start(self):
        """启动 - 自动开始监听默认目录"""
        if not self.watch_dirs:
            # 默认监听 store 目录
            self.watch_dirs = ["store"]
        self.start_watching()

    def stop(self):
        """停止"""
        if self.watcher:
            self.watcher.stop()

    def set_plugin_loader(self, plugin_loader):
        """设置插件加载器实例"""
        self.plugin_loader_instance = plugin_loader

    def set_watch_dirs(self, dirs: list[str]):
        """设置监听目录"""
        self.watch_dirs = dirs

    def start_watching(self):
        """开始监听文件变化"""
        if self.watch_dirs and self.plugin_loader_instance:
            self.watcher = FileWatcher(
                self.watch_dirs,
                self.watch_extensions,
                self._on_file_change
            )
            self.watcher.start()

    def _on_file_change(self, changes: list[tuple[str, Path]]):
        """文件变化回调"""
        for change_type, fpath in changes:
            # 只关心 main.py 和 manifest.json 的变化
            if fpath.name not in ("main.py", "manifest.json"):
                continue

            plugin_dir = fpath.parent
            plugin_name = plugin_dir.name

            try:
                if change_type == "new":
                    self.load_plugin(plugin_dir)
                elif change_type == "modified":
                    self.reload_plugin(plugin_name, plugin_dir)
                elif change_type == "deleted":
                    self.unload_plugin(plugin_name)
            except Exception as e:
                Log.error("hot-reload", f"处理变化失败: {e}")

    def load_plugin(self, plugin_dir: Path) -> bool:
        """运行时加载插件"""
        try:
            plugin_name = plugin_dir.name
            if plugin_name in self.plugin_loader_instance.plugins:
                raise HotReloadError(f"插件已存在: {plugin_name}")

            self.plugin_loader_instance.load(plugin_dir)
            info = self.plugin_loader_instance.plugins[plugin_name]
            instance = info["instance"]
            instance.init()
            instance.start()
            return True
        except Exception as e:
            raise HotReloadError(f"加载插件失败: {e}")

    def unload_plugin(self, plugin_name: str) -> bool:
        """运行时卸载插件"""
        try:
            if plugin_name not in self.plugin_loader_instance.plugins:
                raise HotReloadError(f"插件不存在: {plugin_name}")

            info = self.plugin_loader_instance.plugins[plugin_name]
            instance = info["instance"]
            instance.stop()

            # 从模块缓存中移除
            module = info.get("module")
            if module and module.__name__ in sys.modules:
                del sys.modules[module.__name__]

            del self.plugin_loader_instance.plugins[plugin_name]
            return True
        except Exception as e:
            raise HotReloadError(f"卸载插件失败: {e}")

    def reload_plugin(self, plugin_name: str, plugin_dir: Path) -> bool:
        """运行时更新插件"""
        try:
            # 先卸载
            self.unload_plugin(plugin_name)
            # 再加载
            return self.load_plugin(plugin_dir)
        except Exception as e:
            raise HotReloadError(f"更新插件失败: {e}")


# 注册类型
register_plugin_type("HotReloadError", HotReloadError)
register_plugin_type("FileWatcher", FileWatcher)


def New():
    return HotReloadPlugin()
