"""Module discovery and reload mechanism for playbook directories."""

from __future__ import annotations

import importlib
import importlib.util
import logging
import sys
from pathlib import Path
from types import ModuleType

logger = logging.getLogger(__name__)


class ModuleDiscovery:
    """掃描指定目錄下所有 .py 模組並 import/reload。

    掃描 blueprints/ 和 portfolios/ 子目錄，觸發 import 執行 decorator 註冊。
    """

    def __init__(self, root: Path) -> None:
        self._root = root
        self._loaded_modules: dict[str, ModuleType] = {}

    @property
    def loaded_count(self) -> int:
        """已載入模組數量。"""
        return len(self._loaded_modules)

    def discover(self) -> list[str]:
        """首次掃描，import blueprints/ + portfolios/ 下所有 .py。

        Returns:
            list[str]: 已載入的模組名稱列表
        """
        loaded: list[str] = []
        for subdir in ("blueprints", "portfolios"):
            dir_path = self._root / subdir
            if not dir_path.is_dir():
                continue
            for py_file in sorted(dir_path.rglob("*.py")):
                if py_file.name.startswith("_"):
                    continue
                mod_name = self._to_module_name(py_file)
                if mod_name in self._loaded_modules:
                    continue
                try:
                    mod = self._import_module(mod_name, py_file)
                    self._loaded_modules[mod_name] = mod
                    loaded.append(mod_name)
                    logger.debug("Discovered module: %s", mod_name)
                except Exception:
                    logger.exception("Failed to import module: %s", mod_name)
        return loaded

    def reload(self) -> list[str]:
        """重新載入所有已知模組 + 掃描新增模組。

        流程：
        1. 對已知模組執行 importlib.reload()（decorator 重新觸發註冊）
        2. 掃描新增 .py 並 import

        Returns:
            list[str]: 已重新載入的模組名稱列表
        """
        reloaded: list[str] = []

        # Reload 已知模組
        for mod_name, mod in list(self._loaded_modules.items()):
            if mod.__file__ is None:
                continue
            try:
                self._import_module(mod_name, Path(mod.__file__))
                reloaded.append(mod_name)
                logger.debug("Reloaded module: %s", mod_name)
            except Exception:
                logger.exception("Failed to reload module: %s", mod_name)
                del self._loaded_modules[mod_name]

        # 掃描新增模組
        new_mods = self.discover()
        reloaded.extend(m for m in new_mods if m not in reloaded)

        return reloaded

    def _import_module(self, mod_name: str, file_path: Path) -> ModuleType:
        """動態 import .py 檔案，處理 sys.modules 快取。"""
        # 確保 root 的 parent 在 sys.path（讓 playbook.xxx 可 import）
        root_parent = str(self._root.parent)
        if root_parent not in sys.path:
            sys.path.insert(0, root_parent)

        # 確保 root 自身也在 sys.path（讓相對 import 正常）
        root_str = str(self._root)
        if root_str not in sys.path:
            sys.path.insert(0, root_str)

        # 清除舊快取
        if mod_name in sys.modules:
            del sys.modules[mod_name]

        spec = importlib.util.spec_from_file_location(mod_name, file_path)
        if spec is None or spec.loader is None:
            msg = f"Cannot create module spec for {file_path}"
            raise ImportError(msg)

        module = importlib.util.module_from_spec(spec)
        sys.modules[mod_name] = module
        spec.loader.exec_module(module)
        return module

    def _to_module_name(self, file_path: Path) -> str:
        """將檔案路徑轉為模組名稱。

        e.g. /path/playbook/blueprints/us100.py → playbook.blueprints.us100
        """
        # 使用 root 的 dirname 名稱作為 package prefix
        root_name = self._root.name
        rel = file_path.relative_to(self._root)
        return f"{root_name}.{str(rel.with_suffix('')).replace('/', '.')}"
