"""Configuration and shared classes for MakePython."""

from __future__ import annotations

import atexit
import json
import logging
import shutil
import subprocess
import time
from dataclasses import dataclass
from enum import Enum
from functools import cached_property, lru_cache
from pathlib import Path
from typing import Any, Callable, Protocol

from ._tool_utils import (
    CONFIG_FILE,
    DEFAULT_ENCODING,
    DEFAULT_MAX_RETRIES,
    DEFAULT_TIMEOUT_SECONDS,
)
from .core.cache import ProjectCache

__version__ = "0.1.8"

logger = logging.getLogger("makepython")


# Global instances
_project_cache = ProjectCache()


@lru_cache(maxsize=128)
def _is_tool_available(tool_name: str) -> bool:
    """检查构建工具是否可用, 使用LRU缓存."""
    return bool(shutil.which(tool_name))


class ToolAvailabilityCache:
    """构建工具可用性缓存,重复调用shutil.which."""

    def __init__(self) -> None:
        self._cache: dict[str, bool] = {}
        self._last_check: dict[str, float] = {}
        self.ttl_seconds = 60  # 1分钟缓存

    def is_available(self, tool_name: str) -> bool:
        """检查工具是否可用,带存."""
        current_time = time.time()

        # 检查缓存是否存在且未过期
        if tool_name in self._cache and current_time - self._last_check[tool_name] < self.ttl_seconds:
            return self._cache[tool_name]

        # 缓存未命中或已过期, 重新检查
        is_avail = _is_tool_available(tool_name)
        self._cache[tool_name] = is_avail
        self._last_check[tool_name] = current_time

        return is_avail

    def invalidate(self, tool_name: str) -> None:
        """使指定工具的缓存失效."""
        if tool_name in self._cache:
            del self._cache[tool_name]
        if tool_name in self._last_check:
            del self._last_check[tool_name]

    def clear(self) -> None:
        """清空所有缓存."""
        self._cache.clear()
        self._last_check.clear()


# 全局工具缓存实例
_tool_cache = ToolAvailabilityCache()


class BuildTool(Enum):
    """Enumeration of supported build tools."""

    UV = "uv"
    POETRY = "poetry"
    HATCH = "hatch"


class CommandExecutor(Protocol):
    """Protocol for command execution."""

    def execute(
        self,
        command: list[str],
        cwd: Path,
    ) -> subprocess.CompletedProcess[str]:
        """Execute a command in the specified directory."""
        ...


@dataclass
class Command:
    """Represents a command that can be executed."""

    name: str
    alias: str
    command_type: str
    cmds: "list[str] | Callable[..., Any] | None" = None
    description: str = ""


class MakePythonConfig:
    """Configuration for MakePython tool."""

    def __init__(self) -> None:
        self.default_build_tool: BuildTool = BuildTool.UV
        self.auto_detect_tool: bool = True
        self.verbose_output: bool = False
        self.max_retries: int = DEFAULT_MAX_RETRIES
        self.timeout_seconds: int = DEFAULT_TIMEOUT_SECONDS
        self._load_from_file()
        atexit.register(self.save)

    def _load_from_file(self) -> None:
        """从文件加载配置."""
        if not CONFIG_FILE.exists():
            return

        try:
            config_data = json.loads(
                CONFIG_FILE.read_text(encoding=DEFAULT_ENCODING),
            )
        except (OSError, json.JSONDecodeError) as e:
            logger.warning(f"无法从 {CONFIG_FILE} 加载配置: {e}")
            return
        except Exception as e:
            logger.exception(f"加载配置时出现意外错误: {e}")
            return

        # 应用配置
        for key, value in config_data.items():
            if hasattr(self, key):
                if key == "default_build_tool":
                    try:
                        setattr(self, key, BuildTool(value))
                    except ValueError:
                        logger.warning(f"无效的构建工具值 '{value}', 使用默认值")
                        setattr(self, key, self.default_build_tool)
                else:
                    setattr(self, key, value)

    def save(self) -> None:
        """Save current configuration to file."""
        try:
            CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
            config_dict = {
                "default_build_tool": self.default_build_tool.value,
                "auto_detect_tool": self.auto_detect_tool,
                "verbose_output": self.verbose_output,
                "max_retries": self.max_retries,
                "timeout_seconds": self.timeout_seconds,
            }
            CONFIG_FILE.write_text(
                json.dumps(config_dict, indent=4),
                encoding=DEFAULT_ENCODING,
            )
            logger.debug(f"Configuration saved to {CONFIG_FILE}")
        except OSError as e:
            logger.exception(f"Failed to save configuration: {e}")
            msg = f"Failed to save configuration: {e}"
            raise ConfigurationError(msg) from e
        except Exception as e:
            logger.exception(f"Unexpected error saving configuration: {e}")
            msg = f"Unexpected error saving configuration: {e}"
            raise ConfigurationError(msg) from e

    @cached_property
    def supported_tools(self) -> list[BuildTool]:
        """获取可用的构建工具列表, 使用缓存优化性能."""
        return [tool for tool in BuildTool if _tool_cache.is_available(tool.value)]


class ConfigurationError(Exception):
    """Raised when configuration loading or saving fails."""


class BuildToolNotFoundError(Exception):
    """Raised when no suitable build tool is found."""


class TokenConfigurationError(Exception):
    """Raised when PyPI token configuration fails."""


class CommandExecutionError(Exception):
    """Raised when command execution fails."""

    def __init__(
        self,
        message: str,
        return_code: int = 1,
        stdout: str = "",
        stderr: str = "",
    ) -> None:
        super().__init__(message, return_code)
        self.stdout = stdout
        self.stderr = stderr
