"""Resource type extension interface for CleverAgents.

Provides the stable public API that third-party developers use to implement
custom resource types without modifying core code.

## Developer Guide: Implementing a Custom Resource Type

### Overview

A *resource type* defines the lifecycle behaviour of a category of resources
(e.g. a cloud database, a message queue, a container registry).  Third-party
developers implement custom types by subclassing :class:`ResourceType` and
registering the class with :func:`register_resource_type`.

### Step 1 — Subclass ResourceType

All five abstract methods must be implemented::

    from cleveragents.resources import ResourceType, ResourceConfig, ResourceStatus

    class MyQueueType(ResourceType):
        \"\"\"Custom message-queue resource type.\"\"\"

        def provision(self, config: ResourceConfig) -> ResourceStatus:
            \"\"\"Create the queue and return ACTIVE on success.\"\"\"
            _create_queue(config.properties["queue_name"])
            return ResourceStatus.ACTIVE

        def deprovision(self, config: ResourceConfig) -> ResourceStatus:
            \"\"\"Delete the queue and return DEPROVISIONED on success.\"\"\"
            _delete_queue(config.properties["queue_name"])
            return ResourceStatus.DEPROVISIONED

        def status(self, config: ResourceConfig) -> ResourceStatus:
            \"\"\"Return the current lifecycle state of the queue.\"\"\"
            if _queue_exists(config.properties["queue_name"]):
                return ResourceStatus.ACTIVE
            return ResourceStatus.FAILED

        def validate_config(self, config: ResourceConfig) -> list[str]:
            \"\"\"Return a list of validation error messages (empty = valid).\"\"\"
            errors: list[str] = []
            if not config.properties.get("queue_name"):
                errors.append("'queue_name' is required in properties")
            return errors

        def to_dict(self) -> dict[str, object]:
            \"\"\"Return a serialisable representation of this type.\"\"\"
            return {
                "type": "myorg/queue",
                "description": "Custom message-queue resource type",
                "version": "1.0.0",
            }

### Step 2 — Register the type

Use :func:`register_resource_type` with a namespaced name::

    from cleveragents.resources import register_resource_type
    register_resource_type("myorg/queue", MyQueueType)

Custom type names **must** follow the ``namespace/name`` format.  Built-in
type names (e.g. ``git-checkout``) are reserved for core use.

### Step 3 — Retrieve and use the type

    from cleveragents.resources import get_resource_type, ResourceConfig

    cls = get_resource_type("myorg/queue")
    if cls is not None:
        instance = cls()
        cfg = ResourceConfig(name="my-queue", resource_type="myorg/queue",
                             properties={"queue_name": "events"})
        status = instance.provision(cfg)

### ResourceStatus lifecycle

Resources transition through the following states:

- ``PENDING``      — provisioning has been requested but not yet completed.
- ``ACTIVE``       — the resource is provisioned and operational.
- ``FAILED``       — provisioning or a health check failed.
- ``DEPROVISIONED``— the resource has been torn down.

Based on:
    - Issue #9998 — feat(resources): design and implement resource type
      extension interface
    - docs/specification.md — Resource Types, Resource Registry
"""

from __future__ import annotations

import abc
from enum import StrEnum
from typing import Any

from pydantic import BaseModel, Field

__all__ = [
    "ResourceConfig",
    "ResourceStatus",
    "ResourceType",
    "get_resource_type",
    "list_resource_types",
    "register_resource_type",
]


# ---------------------------------------------------------------------------
# ResourceStatus
# ---------------------------------------------------------------------------


class ResourceStatus(StrEnum):
    """Lifecycle state of a provisioned resource.

    Attributes:
        PENDING: Provisioning has been requested but not yet completed.
        ACTIVE: The resource is provisioned and operational.
        FAILED: Provisioning or a health check failed.
        DEPROVISIONED: The resource has been torn down.
    """

    PENDING = "PENDING"
    ACTIVE = "ACTIVE"
    FAILED = "FAILED"
    DEPROVISIONED = "DEPROVISIONED"


# ---------------------------------------------------------------------------
# ResourceConfig
# ---------------------------------------------------------------------------


class ResourceConfig(BaseModel):
    """Configuration container passed to :class:`ResourceType` lifecycle methods.

    Attributes:
        name: Human-readable name for the resource instance.
        resource_type: Namespaced type identifier (e.g. ``myorg/database``).
        properties: Arbitrary key-value configuration for the resource.
            Defaults to an empty dict.
    """

    name: str
    resource_type: str
    properties: dict[str, Any] = Field(default_factory=dict)


# ---------------------------------------------------------------------------
# ResourceType ABC
# ---------------------------------------------------------------------------


class ResourceType(abc.ABC):
    """Abstract base class for all resource type implementations.

    Third-party developers subclass this class to add custom resource types
    to CleverAgents without modifying core code.  All five lifecycle methods
    are abstract and **must** be implemented by concrete subclasses.

    Lifecycle methods:
        provision: Create and initialise the resource.
        deprovision: Tear down and clean up the resource.
        status: Return the current lifecycle state.
        validate_config: Validate a :class:`ResourceConfig` before use.
        to_dict: Return a serialisable representation of this type.

    See the module-level developer guide for a complete implementation example.
    """

    @abc.abstractmethod
    def provision(self, config: ResourceConfig) -> ResourceStatus:
        """Provision the resource described by *config*.

        Args:
            config: Configuration for the resource instance to create.

        Returns:
            :attr:`ResourceStatus.ACTIVE` on success,
            :attr:`ResourceStatus.FAILED` on failure.
        """

    @abc.abstractmethod
    def deprovision(self, config: ResourceConfig) -> ResourceStatus:
        """Deprovision (tear down) the resource described by *config*.

        Args:
            config: Configuration identifying the resource instance to remove.

        Returns:
            :attr:`ResourceStatus.DEPROVISIONED` on success,
            :attr:`ResourceStatus.FAILED` on failure.
        """

    @abc.abstractmethod
    def status(self, config: ResourceConfig) -> ResourceStatus:
        """Return the current lifecycle state of the resource.

        Args:
            config: Configuration identifying the resource instance to check.

        Returns:
            The current :class:`ResourceStatus` of the resource.
        """

    @abc.abstractmethod
    def validate_config(self, config: ResourceConfig) -> list[str]:
        """Validate *config* before provisioning or status checks.

        Args:
            config: Configuration to validate.

        Returns:
            A list of human-readable error messages.  An empty list means
            the configuration is valid.
        """

    @abc.abstractmethod
    def to_dict(self) -> dict[str, object]:
        """Return a serialisable representation of this resource type.

        Returns:
            A dict suitable for JSON/YAML serialisation describing this
            resource type (e.g. name, version, description).
        """


# ---------------------------------------------------------------------------
# Registry
# ---------------------------------------------------------------------------

#: Internal registry mapping type names to ResourceType subclasses.
_registry: dict[str, type[ResourceType]] = {}


def register_resource_type(name: str, cls: type[ResourceType]) -> None:
    """Register a custom resource type class by name.

    Args:
        name: Namespaced type name (e.g. ``myorg/database``).
        cls: A concrete subclass of :class:`ResourceType`.

    Raises:
        TypeError: If *cls* is not a subclass of :class:`ResourceType`.
        ValueError: If *name* is already registered.
    """
    if not (isinstance(cls, type) and issubclass(cls, ResourceType)):
        raise TypeError(
            f"'{cls}' is not a subclass of ResourceType. "
            "Only ResourceType subclasses can be registered."
        )
    if name in _registry:
        raise ValueError(
            f"Resource type '{name}' is already registered. "
            "Use a unique name or unregister the existing type first."
        )
    _registry[name] = cls


def get_resource_type(name: str) -> type[ResourceType] | None:
    """Retrieve a registered resource type class by name.

    Args:
        name: The namespaced type name used during registration.

    Returns:
        The registered :class:`ResourceType` subclass, or ``None``.
    """
    return _registry.get(name)


def list_resource_types() -> list[str]:
    """Return a sorted list of all registered resource type names."""
    return sorted(_registry.keys())
