"""ToolRegistry — singleton registry of available development tools."""

from __future__ import annotations

import copy
import os
from pathlib import Path
from typing import Any

import structlog

from proxym.tools._enums import ToolCategory
from proxym.tools._definition import ToolDefinition
from proxym.tools._builtins import BUILTINS

logger = structlog.get_logger()

_TOOL_OVERRIDES_NAMESPACE = "tool_overrides"
_TOOL_OVERRIDE_FIELDS = {"default_model", "default_instance", "description", "force_available"}


class ToolRegistry:
    """Singleton registry of available development tools."""

    _instance: ToolRegistry | None = None

    def __init__(self, persist_path: Path | None = None) -> None:
        self._persist_path = persist_path
        self._store: Any = None
        if self._persist_path is not None:
            from proxym.storage import SQLiteNamespaceStore
            self._store = SQLiteNamespaceStore(self._persist_path)
        self._tools: dict[str, ToolDefinition] = {}
        for t in BUILTINS:
            self._tools[t.name] = copy.copy(t)
        self._apply_env_overrides()
        self._load_overrides()
        logger.info("tool_registry_init", count=len(self._tools),
                    available=len(self.list_available()))

    def _apply_env_overrides(self) -> None:
        """Mark tools available via PROXYM_TOOLS_AVAILABLE env var.

        Value: comma-separated tool names, or ``*`` for all.
        Useful when the server runs inside a container but dispatches
        to host-side binaries.
        """
        raw = os.environ.get("PROXYM_TOOLS_AVAILABLE", "").strip()
        if not raw:
            return
        if raw == "*":
            names = set(self._tools.keys())
        else:
            names = {n.strip() for n in raw.split(",") if n.strip()}
        for name in names:
            tool = self._tools.get(name)
            if tool:
                tool.force_available = True
                logger.debug("tool_force_available", name=name)

    @classmethod
    def get(cls, persist_path: Path | None = None) -> ToolRegistry:
        """Return singleton instance."""
        if cls._instance is None:
            if persist_path is None:
                from proxym.storage import DEFAULT_SQLITE_PATH
                persist_path = DEFAULT_SQLITE_PATH
            cls._instance = cls(persist_path)
        return cls._instance

    def register(self, tool: ToolDefinition) -> None:
        """Register or override a tool definition."""
        self._tools[tool.name] = tool
        logger.info("tool_registered", name=tool.name)

    def unregister(self, name: str) -> bool:
        """Remove a tool. Returns False if not found."""
        if name in self._tools:
            del self._tools[name]
            return True
        return False

    def get_tool(self, name: str) -> ToolDefinition | None:
        return self._tools.get(name)

    def list_tools(
        self,
        category: ToolCategory | None = None,
        available_only: bool = False,
    ) -> list[ToolDefinition]:
        tools = list(self._tools.values())
        if category:
            tools = [t for t in tools if t.category == category]
        if available_only:
            tools = [t for t in tools if t.is_available]
        return sorted(tools, key=lambda t: t.name)

    def update_tool(self, name: str, updates: dict[str, Any]) -> ToolDefinition | None:
        """Update mutable fields of a tool definition and persist. Returns updated tool or None."""
        tool = self._tools.get(name)
        if not tool:
            return None
        persisted_keys = []
        for key, value in updates.items():
            if key in _TOOL_OVERRIDE_FIELDS and hasattr(tool, key):
                setattr(tool, key, value)
                persisted_keys.append(key)
        if persisted_keys:
            self._save_overrides()
        logger.info("tool_updated", name=name, updates=persisted_keys)
        return tool

    # ── Persistence ───────────────────────────────────────────

    def _load_overrides(self) -> None:
        """Load user overrides from SQLite and apply on top of builtins."""
        if self._store is None:
            return
        try:
            data = self._store.load_namespace(_TOOL_OVERRIDES_NAMESPACE)
            applied = 0
            for tool_name, overrides in data.items():
                tool = self._tools.get(tool_name)
                if not tool or not isinstance(overrides, dict):
                    continue
                for key, value in overrides.items():
                    if key in _TOOL_OVERRIDE_FIELDS and hasattr(tool, key):
                        setattr(tool, key, value)
                        applied += 1
            if applied:
                logger.info("tool_overrides_loaded", count=applied)
        except Exception as exc:
            logger.warning("tool_overrides_load_failed", error=str(exc))

    def _save_overrides(self) -> None:
        """Persist only user-changed fields (diff vs builtins) to SQLite."""
        if self._persist_path is None:
            return
        if self._store is None:
            from proxym.storage import SQLiteNamespaceStore
            self._store = SQLiteNamespaceStore(self._persist_path)
        builtin_map = {t.name: t for t in BUILTINS}
        overrides: dict[str, dict[str, Any]] = {}
        for name, tool in self._tools.items():
            builtin = builtin_map.get(name)
            diff: dict[str, Any] = {}
            for field_name in _TOOL_OVERRIDE_FIELDS:
                current = getattr(tool, field_name, None)
                default = getattr(builtin, field_name, None) if builtin else None
                if current != default:
                    diff[field_name] = current
            if diff:
                overrides[name] = diff
        self._store.save_namespace(_TOOL_OVERRIDES_NAMESPACE, overrides)

    def list_available(self) -> list[ToolDefinition]:
        return self.list_tools(available_only=True)

    def summary(self) -> dict[str, Any]:
        tools = self.list_tools()
        available = [t for t in tools if t.is_available]
        return {
            "total": len(tools),
            "available": len(available),
            "tools": [t.summary() for t in tools],
        }
