from __future__ import annotations

from typing import Any

from django.apps import apps
from django.db import models

from django_anyllm.permissions import check_permission, validate_permissions


class ModelNotRegistered(Exception):
    """Raised when an operation targets a model that is not in the registry."""


class ModelRegistration:
    """Holds registration metadata for a single model."""

    def __init__(
        self,
        model: type[models.Model],
        permissions: set[str] | None = None,
    ) -> None:
        self.model = model
        self.permissions = permissions or {"read", "create", "update", "delete"}
        validate_permissions(self.permissions)


class ModelRegistry:
    """A registry of Django models exposed to LLM tools.

    Example::

        registry = ModelRegistry()
        registry.register(MyModel, permissions={"read", "create"})
    """

    def __init__(self) -> None:
        self._registrations: dict[str, ModelRegistration] = {}

    def _key(self, app_label: str, model_name: str) -> str:
        return f"{app_label}.{model_name}"

    def register(
        self,
        model: type[models.Model] | str,
        permissions: set[str] | None = None,
    ) -> None:
        """Register a model (or its ``'app_label.ModelName'`` string) with the registry.

        Args:
            model: A Django model class or string label.
            permissions: Set of allowed actions. Defaults to all CRUD.

        Raises:
            LookupError: If the string label cannot be resolved.
            ValueError: If an invalid permission is provided.
        """
        if isinstance(model, str):
            model = apps.get_model(model)

        key = self._key(model._meta.app_label, model._meta.model_name)
        self._registrations[key] = ModelRegistration(model, permissions)

    def unregister(self, app_label: str, model_name: str) -> None:
        """Remove a model from the registry.

        Args:
            app_label: The app's label.
            model_name: The lower-case model name.
        """
        key = self._key(app_label, model_name)
        self._registrations.pop(key, None)

    def is_registered(self, app_label: str, model_name: str) -> bool:
        """Return whether the model is registered."""
        return self._key(app_label, model_name) in self._registrations

    def get_registration(self, app_label: str, model_name: str) -> ModelRegistration:
        """Return the ``ModelRegistration`` for a model.

        Raises:
            ModelNotRegistered: If the model is not in the registry.
        """
        key = self._key(app_label, model_name)
        try:
            return self._registrations[key]
        except KeyError as exc:
            msg = f"Model '{key}' is not registered."
            raise ModelNotRegistered(msg) from exc

    def get_model(self, app_label: str, model_name: str) -> type[models.Model]:
        """Return the model class for a registered model.

        Raises:
            ModelNotRegistered: If the model is not in the registry.
        """
        return self.get_registration(app_label, model_name).model

    def check_permission(self, app_label: str, model_name: str, action: str) -> None:
        """Verify that *action* is allowed for the registered model.

        Raises:
            ModelNotRegistered: If the model is not in the registry.
            PermissionDenied: If the action is not permitted.
        """
        reg = self.get_registration(app_label, model_name)
        check_permission(reg.permissions, action)

    def list_models(self) -> list[dict[str, Any]]:
        """Return a list of metadata dicts for every registered model."""
        return [
            {
                "app_label": reg.model._meta.app_label,
                "model_name": reg.model._meta.model_name,
                "verbose_name": str(reg.model._meta.verbose_name),
                "verbose_name_plural": str(reg.model._meta.verbose_name_plural),
                "docstring": (reg.model.__doc__ or "").strip(),
                "permissions": sorted(reg.permissions),
            }
            for reg in self._registrations.values()
        ]


# Global default registry for convenience.
default_registry = ModelRegistry()
