# coding=utf-8
"""
"""
from __future__ import absolute_import, division, print_function

import abc

import sqlalchemy as sa
from sqlalchemy import Column, ForeignKey, Integer, UnicodeText
from sqlalchemy.orm import backref, relationship

from abilian.core.entities import Entity
from abilian.services.security import CREATE, DELETE, WRITE, Anonymous, Owner

#: name of backref on target :class:`Entity` object
ATTRIBUTE = '__comments__'


class Commentable(object):
    __metaclass__ = abc.ABCMeta


def register(cls):
    """Register an :class:`Entity` as a commentable class.

    Can be used as a class decorator:

    .. code-block:: python

      @comment.register
      class MyContent(Entity):
          ...
    """
    if not issubclass(cls, Entity):
        raise ValueError(
            'Class must be a subclass of abilian.core.entities.Entity')

    Commentable.register(cls)
    return cls


def is_commentable(obj):
    """
    :param obj: a class or instance
    """
    if isinstance(obj, type):
        return issubclass(obj, Commentable)

    if not isinstance(obj, Commentable):
        return False

    if obj.id is None:
        return False

    return True


def for_entity(obj, check_commentable=False):
    """Return comments on an entity.
    """
    if check_commentable and not is_commentable(obj):
        return []

    return getattr(obj, ATTRIBUTE)


class Comment(Entity):
    """A Comment related to an :class:`Entity`.
    """
    __default_permissions__ = {
        WRITE: {Owner},
        DELETE: {Owner},
        CREATE: {Anonymous},
    }

    @sa.ext.declarative.declared_attr
    def __mapper_args__(cls):
        # we cannot use super(Comment, cls): declared_attr happens during class
        # construction. super(cls, cls) could work; as long as `cls` is not a
        # subclass of `Comment`: it would enter into an infinite loop.
        #
        # Entity.__mapper_args__ calls the descriptor with 'Entity', not `cls`.
        args = Entity.__dict__['__mapper_args__'].fget(cls)
        args['order_by'] = cls.created_at
        return args

    entity_id = Column(Integer, ForeignKey(Entity.id), nullable=False)

    #: Commented entity
    entity = relationship(Entity,
                          lazy='immediate',
                          foreign_keys=[entity_id],
                          backref=backref(ATTRIBUTE,
                                          lazy='select',
                                          order_by='Comment.created_at',
                                          cascade="all, delete-orphan",))

    #: comment's main content
    body = Column(UnicodeText(),
                  sa.CheckConstraint("trim(body) != ''"),
                  nullable=False)

    @property
    def history(self):
        return self.meta.get('abilian.core.models.comment', {}).get('history',
                                                                    [])

    def __repr__(self):
        class_ = self.__class__
        mod_ = class_.__module__
        classname = class_.__name__
        return '<{}.{} instance at 0x{:x} entity id={!r}'\
            .format(mod_, classname, id(self), self.entity_id)
