"""Factory for creating sandbox instances from resource metadata.

The factory maps a sandbox strategy string to the appropriate sandbox
implementation class.  When the Resource domain model (Stage B1) is
available, this factory will accept ``Resource`` objects directly; until
then it operates on raw parameters.

Stage B3.6 of the implementation plan.  Updated in TASK-006 (B4 rework).
Updated in M6+ to support custom sandbox strategy resolution (#586).
"""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Literal

from cleveragents.infrastructure.sandbox.copy_on_write import CopyOnWriteSandbox
from cleveragents.infrastructure.sandbox.git_worktree import GitWorktreeSandbox
from cleveragents.infrastructure.sandbox.no_sandbox import NoSandbox
from cleveragents.infrastructure.sandbox.overlay import OverlaySandbox
from cleveragents.infrastructure.sandbox.protocol import (
    Sandbox,
)
from cleveragents.infrastructure.sandbox.transaction_sandbox import TransactionSandbox

if TYPE_CHECKING:
    from cleveragents.infrastructure.sandbox.strategy_registry import (
        SandboxStrategyRegistry,
    )

logger = logging.getLogger(__name__)

# Strategy string constants aligned with spec SandboxStrategy enum (6 values)
STRATEGY_GIT_WORKTREE: Literal["git_worktree"] = "git_worktree"
STRATEGY_COPY_ON_WRITE: Literal["copy_on_write"] = "copy_on_write"
STRATEGY_TRANSACTION_ROLLBACK: Literal["transaction_rollback"] = "transaction_rollback"
STRATEGY_SNAPSHOT: Literal["snapshot"] = "snapshot"
STRATEGY_OVERLAY: Literal["overlay"] = "overlay"
STRATEGY_NONE: Literal["none"] = "none"

SandboxStrategyStr = Literal[
    "git_worktree",
    "copy_on_write",
    "transaction_rollback",
    "snapshot",
    "overlay",
    "none",
]

# Strategies that have concrete implementations
_IMPLEMENTED_STRATEGIES: frozenset[str] = frozenset(
    {"none", "git_worktree", "copy_on_write", "transaction_rollback", "overlay"}
)

# Resource type to supported strategies mapping (spec-aligned)
_SUPPORTED_STRATEGIES: dict[str, list[SandboxStrategyStr]] = {
    "git-checkout": ["git_worktree", "copy_on_write", "none"],
    "git": ["none"],
    "fs-mount": ["copy_on_write", "overlay", "none"],
    "fs-directory": ["copy_on_write", "overlay", "none"],
    "fs-file": ["copy_on_write", "overlay", "none"],
    "api_endpoint": ["none"],
    "postgres": ["transaction_rollback", "none"],
    "mysql": ["transaction_rollback", "none"],
    "sqlite": ["transaction_rollback", "none"],
    "duckdb": ["transaction_rollback", "none"],
}


class SandboxFactory:
    """Factory for creating :class:`Sandbox` instances.

    Maps a ``sandbox_strategy`` to the appropriate implementation.
    Currently available:

    - ``"none"`` -> :class:`NoSandbox`
    - ``"git_worktree"`` -> :class:`GitWorktreeSandbox`
    - ``"copy_on_write"`` -> :class:`CopyOnWriteSandbox`
    - ``"transaction_rollback"`` -> :class:`TransactionSandbox`
    - ``"overlay"`` -> :class:`OverlaySandbox`

    ``"snapshot"`` raises ``NotImplementedError``.

    Custom strategies registered in a :class:`SandboxStrategyRegistry` can
    be resolved by passing the registry at construction time.
    """

    def __init__(
        self,
        *,
        custom_registry: SandboxStrategyRegistry | None = None,
    ) -> None:
        """Initialise the factory.

        Args:
            custom_registry: Optional registry of custom sandbox strategies.
                When provided, the factory will fall back to this registry
                for strategy names not matched by built-in strategies.
        """
        self._custom_registry = custom_registry

    def create_sandbox(
        self,
        resource_id: str,
        original_path: str,
        sandbox_strategy: SandboxStrategyStr,
    ) -> Sandbox:
        """Create a sandbox for the given resource parameters.

        Args:
            resource_id: Unique identifier for the resource.
            original_path: Filesystem path or location of the resource.
            sandbox_strategy: Which sandboxing strategy to use.

        Returns:
            A :class:`Sandbox` instance in ``PENDING`` status.

        Raises:
            ValueError: If *resource_id* or *original_path* is empty.
            ValueError: If *sandbox_strategy* is unknown.
            NotImplementedError: If the strategy is known but not yet
                implemented.
        """
        if not resource_id:
            raise ValueError("resource_id cannot be empty")
        if not original_path:
            raise ValueError("original_path cannot be empty")

        if sandbox_strategy == STRATEGY_NONE:
            return NoSandbox(
                resource_id=resource_id,
                original_path=original_path,
            )

        if sandbox_strategy == STRATEGY_GIT_WORKTREE:
            return GitWorktreeSandbox(
                resource_id=resource_id,
                original_path=original_path,
            )

        if sandbox_strategy == STRATEGY_COPY_ON_WRITE:
            return CopyOnWriteSandbox(
                resource_id=resource_id,
                original_path=original_path,
            )

        if sandbox_strategy == STRATEGY_TRANSACTION_ROLLBACK:
            return TransactionSandbox(
                resource_id=resource_id,
                original_path=original_path,
            )

        if sandbox_strategy == STRATEGY_OVERLAY:
            return OverlaySandbox(
                resource_id=resource_id,
                original_path=original_path,
            )

        if sandbox_strategy == STRATEGY_SNAPSHOT:
            raise NotImplementedError("Snapshot sandbox not yet implemented")

        raise ValueError(f"Unknown sandbox strategy: {sandbox_strategy}")

    # -- custom strategy lookup ----------------------------------------------

    def has_custom_strategy(self, name: str) -> bool:
        """Check whether a custom strategy is registered.

        Args:
            name: Strategy name to check.

        Returns:
            ``True`` if a custom strategy with that name exists.
        """
        if self._custom_registry is None:
            return False
        return self._custom_registry.has(name)

    def get_custom_strategy_class(self, name: str) -> type[object] | None:
        """Look up a custom strategy class by name.

        Args:
            name: Strategy name.

        Returns:
            The strategy class, or ``None`` if not found.
        """
        if self._custom_registry is None:
            return None
        return self._custom_registry.get(name)

    # -- validation helpers --------------------------------------------------

    @staticmethod
    def is_supported(sandbox_strategy: str) -> bool:
        """Check whether *sandbox_strategy* has a concrete implementation.

        Args:
            sandbox_strategy: The strategy string to check.

        Returns:
            ``True`` if a concrete sandbox class exists for the strategy.
        """
        return sandbox_strategy in _IMPLEMENTED_STRATEGIES

    @staticmethod
    def get_supported_strategies(resource_type: str) -> list[SandboxStrategyStr]:
        """Return the list of strategies compatible with a resource type.

        Args:
            resource_type: The resource type string (e.g. ``"git-checkout"``).

        Returns:
            List of compatible strategy strings. Returns ``["none"]`` for
            unknown resource types.
        """
        return _SUPPORTED_STRATEGIES.get(resource_type, ["none"])
