"""Dynamic handler resolution from ``module:ClassName`` strings.

The :func:`resolve_handler` function takes a handler reference string
(as stored on :class:`ResourceTypeSpec`) and returns an instantiated
handler object that satisfies the :class:`ResourceHandler` protocol.

Format::

    "cleveragents.resource.handlers.git_checkout:GitCheckoutHandler"
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
               module path                       class name

The function uses :func:`importlib.import_module` for dynamic loading,
with a cache to avoid repeated imports.

Based on:
    - implementation_plan.md group M1.resource-handlers (L2254-L2271)
"""

from __future__ import annotations

import importlib
import logging
import threading
from typing import Any

from cleveragents.resource.handlers.protocol import ResourceHandler

__all__ = [
    "HandlerResolutionError",
    "clear_handler_cache",
    "resolve_handler",
    "resolve_handler_polymorphic",
]

logger = logging.getLogger(__name__)

# Cache of already-resolved handler instances keyed by reference string
_handler_cache: dict[str, ResourceHandler] = {}
_cache_lock: threading.Lock = threading.Lock()


class HandlerResolutionError(Exception):
    """Raised when a handler reference string cannot be resolved."""


def resolve_handler(handler_ref: str) -> ResourceHandler:
    """Resolve a handler reference string to an instance.

    Args:
        handler_ref: A string in ``module.path:ClassName`` format.

    Returns:
        An instantiated handler that satisfies :class:`ResourceHandler`.

    Raises:
        HandlerResolutionError: If the reference is malformed, the
            module cannot be imported, or the class does not exist.
    """
    if not handler_ref or not handler_ref.strip():
        raise HandlerResolutionError("Handler reference must not be empty")

    handler_ref = handler_ref.strip()

    # Return cached instance if available
    with _cache_lock:
        if handler_ref in _handler_cache:
            return _handler_cache[handler_ref]

    # Parse module:class format
    if ":" not in handler_ref:
        raise HandlerResolutionError(
            f"Invalid handler reference format '{handler_ref}': "
            "expected 'module.path:ClassName'"
        )

    module_path, class_name = handler_ref.rsplit(":", 1)
    if not module_path or not class_name:
        raise HandlerResolutionError(
            f"Invalid handler reference '{handler_ref}': "
            "both module path and class name are required"
        )

    # Import module
    try:
        module = importlib.import_module(module_path)
    except ModuleNotFoundError as exc:
        raise HandlerResolutionError(
            f"Cannot import handler module '{module_path}': {exc}"
        ) from exc
    except Exception as exc:
        raise HandlerResolutionError(
            f"Error importing handler module '{module_path}': {exc}"
        ) from exc

    # Get class
    handler_cls: Any = getattr(module, class_name, None)
    if handler_cls is None:
        raise HandlerResolutionError(
            f"Handler class '{class_name}' not found in module '{module_path}'"
        )

    # Instantiate
    try:
        instance = handler_cls()
    except Exception as exc:
        raise HandlerResolutionError(
            f"Cannot instantiate handler '{handler_ref}': {exc}"
        ) from exc

    # Validate protocol conformance
    if not isinstance(instance, ResourceHandler):
        raise HandlerResolutionError(
            f"Handler '{handler_ref}' does not satisfy ResourceHandler protocol"
        )

    # Cache and return
    with _cache_lock:
        _handler_cache[handler_ref] = instance
    logger.debug("Resolved handler: %s", handler_ref)
    return instance


def resolve_handler_polymorphic(
    type_name: str,
    type_registry: dict[str, Any],
) -> ResourceHandler:
    """Resolve a handler for *type_name*, falling back to ancestor types.

    Implements ADR-042 §Handler Inheritance: if the type has no direct
    handler, walk up the inheritance chain and use the first ancestor's
    handler.

    Args:
        type_name: Resource type name.
        type_registry: Dict mapping type names to definitions.  Each
            entry must support ``handler`` and ``inherits`` keys.

    Returns:
        An instantiated handler.

    Raises:
        HandlerResolutionError: If no handler is found in the chain.
    """
    from cleveragents.resource.inheritance import (
        ResourceTypeCircularInheritanceError,
        ResourceTypeInheritanceDepthError,
        ResourceTypeParentNotFoundError,
        resolve_inheritance_chain,
    )

    try:
        chain = resolve_inheritance_chain(type_name, type_registry)
    except (
        ResourceTypeParentNotFoundError,
        ResourceTypeCircularInheritanceError,
        ResourceTypeInheritanceDepthError,
    ) as exc:
        raise HandlerResolutionError(
            f"Cannot resolve handler for '{type_name}': {exc}"
        ) from exc

    for ancestor in chain:
        entry = type_registry.get(ancestor)
        if entry is None:
            continue
        handler_ref = (
            entry.get("handler")
            if isinstance(entry, dict)
            else getattr(entry, "handler", None)
        )
        if handler_ref:
            return resolve_handler(str(handler_ref))

    raise HandlerResolutionError(
        f"No handler found for '{type_name}' or any of its ancestors: "
        f"{' -> '.join(chain)}"
    )


def clear_handler_cache() -> None:
    """Clear the handler instance cache (useful for testing)."""
    with _cache_lock:
        _handler_cache.clear()
