"""
SQLAlchemy JSONAPI Test App.

Colton Provias <cj@coltonprovias.com>
MIT License
"""

from uuid import uuid4

from flask import Flask, request
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Boolean, Column, ForeignKey, Unicode, UnicodeText, Table
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import backref, relationship, validates
from sqlalchemy_jsonapi import (
    FlaskJSONAPI, Permissions, permission_test, Method, Endpoint,
    relationship_descriptor, RelationshipActions,
    INTERACTIVE_PERMISSIONS)
from sqlalchemy_utils import EmailType, PasswordType, Timestamp, UUIDType

app = Flask(__name__)

app.testing = True

db = SQLAlchemy(app)

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
app.config['SQLALCHEMY_ECHO'] = False


class User(Timestamp, db.Model):
    """Quick and dirty user model."""

    #: If __jsonapi_type__ is not provided, it will use the class name instead.
    __tablename__ = 'users'

    id = Column(UUIDType, default=uuid4, primary_key=True)
    username = Column(Unicode(30), unique=True, nullable=False)
    email = Column(EmailType, nullable=False)
    password = Column(PasswordType(schemes=['bcrypt']),
                      nullable=False,
                      info={'allow_serialize': False})
    is_admin = Column(Boolean, default=False)

    @hybrid_property
    def total_comments(self):
        """
        Total number of comments.

        Provides an example of a computed property.
        """
        return self.comments.count()

    @validates('email')
    def validate_email(self, key, email):
        """Strong email validation."""
        assert '@' in email, 'Not an email'
        return email

    @validates('username')
    def validate_username(self, key, username):
        """
        Check the length of the username.

        Here's hoping nobody submits something in unicode that is 31 characters
        long!!
        """
        assert len(username) >= 4 and len(
            username) <= 30, 'Must be 4 to 30 characters long.'
        return username

    @validates('password')
    def validate_password(self, key, password):
        """Validate a password's length."""
        assert len(password) >= 5, 'Password must be 5 characters or longer.'
        return password

    @permission_test(Permissions.VIEW, 'password')
    def view_password(self):
        """ Never let the password be seen. """
        return False

    @permission_test(Permissions.EDIT)
    def prevent_edit(self):
        """ Prevent editing for no reason. """
        if request.view_args['api_type'] == 'blog-posts':
            return True
        return False

    @permission_test(Permissions.DELETE)
    def allow_delete(self):
        """ Just like a popular social media site, we won't delete users. """
        return False

PostTags = Table('post_tag', db.Model.metadata,
        Column('post_id', UUIDType, ForeignKey('posts.id')),
        Column('tag_id', UUIDType, ForeignKey('tags.id'))
        )

class BlogPost(Timestamp, db.Model):
    """Post model, as if this is a blog."""

    __tablename__ = 'posts'

    id = Column(UUIDType, default=uuid4, primary_key=True)
    title = Column(Unicode(100), nullable=False)
    slug = Column(Unicode(100))
    content = Column(UnicodeText, nullable=False)
    is_published = Column(Boolean, default=False)
    author_id = Column(UUIDType, ForeignKey('users.id'))

    author = relationship('User',
                          lazy='joined',
                          backref=backref('posts',
                                          lazy='dynamic'))

    tags = relationship("BlogTag",
            secondary=PostTags,
            back_populates="posts")

    @validates('title')
    def validate_title(self, key, title):
        """Keep titles from getting too long."""
        assert len(title) >= 5 or len(
            title) <= 100, 'Must be 5 to 100 characters long.'
        return title

    @permission_test(Permissions.VIEW)
    def allow_view(self):
        """ Hide unpublished. """
        return self.is_published

    @permission_test(INTERACTIVE_PERMISSIONS, 'logs')
    def prevent_altering_of_logs(self):
        return False

class BlogTag(Timestamp, db.Model):
    """Blogs can have tags now"""

    __tablename__ = 'tags'

    id = Column(UUIDType, default=uuid4, primary_key=True)
    slug = Column(Unicode(100), unique=True)
    description = Column(UnicodeText)

    posts = relationship("BlogPost",
            secondary=PostTags,
            back_populates="tags")

class BlogComment(Timestamp, db.Model):
    """Comment for each Post."""

    __tablename__ = 'comments'

    id = Column(UUIDType, default=uuid4, primary_key=True)
    post_id = Column(UUIDType, ForeignKey('posts.id'))
    author_id = Column(UUIDType, ForeignKey('users.id'), nullable=False)
    content = Column(UnicodeText, nullable=False)

    post = relationship('BlogPost',
                        lazy='joined',
                        backref=backref('comments',
                                        lazy='dynamic'))

    @relationship_descriptor(RelationshipActions.GET, 'post')
    def post_get(self):
        """No-OP Relationship descriptor to exercise relationship_descriptor"""
        return self.post

    author = relationship('User',
                          lazy='joined',
                          backref=backref('comments',
                                          lazy='dynamic'))


class Log(Timestamp, db.Model):
    __tablename__ = 'logs'
    id = Column(UUIDType, default=uuid4, primary_key=True)
    post_id = Column(UUIDType, ForeignKey('posts.id'))
    user_id = Column(UUIDType, ForeignKey('users.id'))

    post = relationship('BlogPost',
                        lazy='joined',
                        backref=backref('logs',
                                        lazy='dynamic'))
    user = relationship('User',
                        lazy='joined',
                        backref=backref('logs',
                                        lazy='dynamic'))

    @permission_test(INTERACTIVE_PERMISSIONS)
    def block_interactive(cls):
        return False


api = FlaskJSONAPI(app, db)


@api.wrap_handler(['blog-posts'], [Method.GET], [Endpoint.COLLECTION])
def sample_override(next, *args, **kwargs):
    return next(*args, **kwargs)


if __name__ == '__main__':
    app.run()