from dataclasses import InitVar, dataclass, field, replace
from pathlib import Path
from typing import Self, cast, override, TYPE_CHECKING, TypedDict
from dais_sdk.tool import PythonToolset, python_tool
from dais_sdk.types import ToolDef
from sqlalchemy.ext.asyncio import AsyncSession
from ..types import ToolMetadata

if TYPE_CHECKING:
    from ....db.models import toolset as toolset_models


builtin_tool = python_tool

class BuiltinToolDefaults(TypedDict, total=False):
    auto_approve: bool

    # Whether this tool needs user interaction (e.g. ask_user, show_plan)
    needs_user_interaction: bool

@dataclass(frozen=True)
class BuiltinToolsetContext:
    cwd: Path = field(init=False)
    task_id: int
    workspace_id: int

    cwd_input: InitVar[str | Path]

    def __post_init__(self, cwd_input: str | Path) -> None:
        object.__setattr__(self, "cwd", Path(cwd_input).expanduser().resolve())

    @classmethod
    def default(cls) -> Self:
        """Create a context for static tool metadata export without runtime state.

        The returned context is only intended for code paths that inspect built-in tool
        definitions, such as tool metadata synchronization. Runtime-only properties are
        intentionally unavailable on this instance.
        """
        return cls(1, 1, Path.cwd())

class BuiltinToolset(PythonToolset):
    def __init__(self,
                 ctx: BuiltinToolsetContext,
                 toolset_ent: toolset_models.Toolset | None = None) -> None:
        self._ctx = ctx
        self._namespaced_tools_cache = super().get_tools(namespaced_tool_name=True)
        self._non_namespaced_tools_cache = super().get_tools(namespaced_tool_name=False)
        if toolset_ent:
            self._tool_ent_map = {tool.internal_key: tool for tool in toolset_ent.tools}
        else:
            self._tool_ent_map = None

    @classmethod
    def internal_key(cls) -> str:
        return cls.__name__

    @classmethod
    async def sync(cls, db_session: AsyncSession):
        from src.services.toolset import ToolsetService

        temp_instance = cls(BuiltinToolsetContext.default())
        raw_tools = super().get_tools(temp_instance, namespaced_tool_name=False)
        toolset_service = ToolsetService(db_session)
        toolset_ent = await toolset_service.get_toolset_by_internal_key(cls.internal_key())
        await toolset_service.sync_toolset(toolset_ent.id,
                                            [ToolsetService.ToolLike(
                                                name=tool.name,
                                                internal_key=temp_instance.format_tool_name(tool.name),
                                                description=tool.description,
                                                auto_approve=cast(BuiltinToolDefaults, tool.defaults).get("auto_approve", False))
                                            for tool in raw_tools])

    def get_original_tools(self, namespaced_tool_name: bool=True) -> list[ToolDef]:
        return super().get_tools(namespaced_tool_name=namespaced_tool_name)

    @override
    def get_tools(self, namespaced_tool_name: bool=True) -> list[ToolDef]:
        if self._tool_ent_map is None:
            raise ValueError("Toolset not initialized")

        result = []
        for namespaced, non_namespaced in zip(self._namespaced_tools_cache, self._non_namespaced_tools_cache):
            # namespaced name of tooldef is the internal_key of the tool entity
            tool_ent = self._tool_ent_map[namespaced.name]
            if not tool_ent.is_enabled: continue

            normalized_tool = namespaced if namespaced_tool_name else non_namespaced
            tool_defaults = cast(BuiltinToolDefaults, normalized_tool.defaults)
            tool_with_metadata = replace(normalized_tool,
                                         metadata=ToolMetadata(
                                            id=tool_ent.id,
                                            auto_approve=tool_ent.auto_approve,
                                            needs_user_interaction=tool_defaults.get("needs_user_interaction", False)))
            result.append(tool_with_metadata)
        return result
