# -*- coding: utf-8 -*-

"""
Base model container.

Provides a base model container, used by the Pyramid traversal
machinery and a mixin to aid with traversal from an instance
back up the tree.
"""

__all__ = [
    "BaseModelContainer",
    "InstanceTraversalMixin",
]

import re
import logging

from zope.interface import implementer
from zope.interface import alsoProvides

from sqlalchemy.exc import InvalidRequestError

from pyramid.interfaces import ILocation
from pyramid.security import ALL_PERMISSIONS
from pyramid.security import Allow, Deny
from pyramid.security import Authenticated, Everyone

from pyramid_basemodel import Session
from pyramid_basemodel.interfaces import IModelContainer
from pyramid_basemodel.root import BaseRoot

valid_slug = re.compile(r"^[.\w-]{1,64}$", re.U)
logger = logging.getLogger(__name__)


def slug_validator(node, value, regexp=valid_slug):
    """
    Validate slug.

    Defaults to using a slug regexp.
    """
    # Raise a ValueError.
    if not regexp.match(value):
        raise ValueError(f"{value} is not a valid slug.")


@implementer(IModelContainer)
class BaseModelContainer(BaseRoot):
    """Traversal factory that looks up model classes by property."""

    property_name = "slug"
    validation_exception = Exception

    @property
    def _validator(self):
        return slug_validator

    # Default container acl to be private whilst granting authenticated
    # users create permission.
    __acl__ = [
        (Allow, "r:admin", ALL_PERMISSIONS),
        (Allow, Authenticated, "view"),
        (Allow, Authenticated, "create"),
        (Deny, Everyone, ALL_PERMISSIONS),
    ]

    @property
    def name(self):
        """Return plurar version of a class name."""
        return self.model_cls.plural_class_name

    @property
    def class_name(self):
        """Determine class name based on the _class_name or the __tablename__."""
        return self.model_cls.class_name

    @property
    def plural_class_name(self):
        """Return plurar version of a class name."""
        return self.model_cls.plural_class_name

    @property
    def class_slug(self):
        """Class slug based on either _class_slug or __tablename__."""
        return self.model_cls.class_slug

    def get_child(self, key):
        """Query for and return the child instance, if found."""
        column = getattr(self.model_cls, self.property_name)
        query = self.model_cls.query.filter(column == key)
        return query.first()

    def __getitem__(self, key):
        """Lookup model instance by key."""
        try:
            self.validator(None, key)
        except self.validation_exception:
            raise KeyError(key)

        context = self.get_child(key)
        if not context:
            raise KeyError(key)

        return self.locatable(context, key)

    def __init__(self, request, model_cls, key=None, parent=None, **kwargs):
        """Instantiate the container."""
        # Compose.
        if key is None:
            key = model_cls.class_slug
        if parent is None:
            parent = BaseRoot(request)

        self.request = request
        self.model_cls = model_cls
        self.__name__ = key
        self.__parent__ = parent
        if "property_name" in kwargs: