"""
Copyright 2019 by J. Christopher Wagner (jwag). All rights reserved.
:license: MIT, see LICENSE for more details.


Complete models for all features when using Flask-SqlAlchemy

BE AWARE: Once any version of this is shipped no changes can be made - instead
a new version needs to be created.
"""

import datetime
from sqlalchemy import (
    Boolean,
    DateTime,
    Column,
    Integer,
    String,
    UnicodeText,
    ForeignKey,
)
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func

from flask_security import RoleMixin, UserMixin


class FsModels(object):
    """
    Helper class for model mixins.
    This records the ``db`` (which is a Flask-SqlAlchemy object) for use in
    mixins.
    """

    roles_users = None
    db = None
    fs_model_version = 1

    @classmethod
    def set_db_info(cls, appdb):
        """ Initialize Model.
        This needs to be called after the DB object has been created
        (e.g. db = Sqlalchemy())
        """
        cls.db = appdb
        cls.roles_users = appdb.Table(
            "roles_users",
            Column("user_id", Integer(), ForeignKey("user.id")),
            Column("role_id", Integer(), ForeignKey("role.id")),
        )


class FsRoleMixin(RoleMixin):
    id = Column(Integer(), primary_key=True)
    name = Column(String(80), unique=True, nullable=False)
    description = Column(String(255))
    # A comma separated list of strings
    permissions = Column(UnicodeText, nullable=True)
    update_datetime = Column(
        DateTime,
        nullable=False,
        server_default=func.now(),
        onupdate=datetime.datetime.utcnow,
    )


class FsUserMixin(UserMixin):
    """ User information
    """

    # flask_security basic fields
    id = Column(Integer, primary_key=True)
    email = Column(String(255), unique=True, nullable=False)
    # Username is important since shouldn't expose email to other users in most cases.
    username = Column(String(255))
    password = Column(String(255), nullable=False)
    active = Column(Boolean(), nullable=False)

    # Faster token checking
    fs_uniquifier = Column(String(64), unique=True, nullable=False)

    # confirmable
    confirmed_at = Column(DateTime())

    # trackable
    last_login_at = Column(DateTime())
    current_login_at = Column(DateTime())
    last_login_ip = Column(String(64))
    current_login_ip = Column(String(64))
    login_count = Column(Integer)

    # 2FA
    tf_primary_method = Column(String(64), nullable=True)
    tf_totp_secret = Column(String(255), nullable=True)
    tf_phone_number = Column(String(128), nullable=True)

    @declared_attr
    def roles(cls):
        return FsModels.db.relationship(
            "Role",
            secondary=FsModels.roles_users,
            backref=FsModels.db.backref("users", lazy="dynamic"),
        )

    create_datetime = Column(DateTime, nullable=False, server_default=func.now())
    update_datetime = Column(
        DateTime,
        nullable=False,
        server_default=func.now(),
        onupdate=datetime.datetime.utcnow,
    )


"""
These are placeholders - not current used
"""


class FsOauth2ClientMixin(object):
    """ Oauth2 client """

    id = Column(String(64), primary_key=True)

    @declared_attr
    def user_id(cls):
        return Column(
            Integer, ForeignKey("user.id", ondelete="CASCADE"), nullable=False
        )

    @declared_attr
    def user(cls):
        return relationship("User")

    grant_type = Column(String(32), nullable=False)
    scopes = Column(UnicodeText(), default="")
    response_type = Column(UnicodeText, nullable=False, default="")
    redirect_uris = Column(UnicodeText())


class FsTokenMixin(object):
    """ (Bearer) Tokens that have been given out """

    id = Column(Integer, primary_key=True)

    @declared_attr
    def client_id(cls):
        return Column(
            Integer, ForeignKey("oauth2_client.id", ondelete="CASCADE"), nullable=False
        )

    # client = relationship("fs_oauth2_client")
    @declared_attr
    def user_id(cls):
        return Column(
            Integer, ForeignKey("user.id", ondelete="CASCADE"), nullable=False
        )

    scopes = Column(UnicodeText(), default="")
    revoked = Column(Boolean(), nullable=False, default=False)
    access_token = Column(String(100), unique=True, nullable=False)
    refresh_token = Column(String(100), unique=True)
    issued_at = Column(DateTime, nullable=False, server_default=func.now())
    expires_at = Column(DateTime())
