import random
import string
import uuid
from hashlib import md5
from datetime import datetime

from sqlalchemy.schema import ForeignKey, Table
from sqlalchemy import Column, Boolean, Unicode, Integer, DateTime, UniqueConstraint
from sqlalchemy.orm import synonym, relationship

from brewman.models.guidtype import GUID
from brewman.models import Base
from brewman.models import DBSession


def encrypt(val):
    return md5(val.encode('utf-8') + "Salt".encode('utf-8')).hexdigest()


class Client(Base):
    __tablename__ = 'auth_clients'

    id = Column('client_id', GUID(), primary_key=True, default=uuid.uuid4)
    code = Column('code', Integer, unique=True, nullable=False)
    name = Column('name', Unicode(255), unique=True, nullable=False)
    enabled = Column('enabled', Boolean, nullable=False)
    otp = Column('otp', Integer)
    creation_date = Column('creation_date', DateTime(timezone=True), nullable=False)

    login_history = relationship('LoginHistory', backref='client')

    def __init__(self, code=None, name=None, enabled=False, otp=None, creation_date=None, id=None):
        self.code = code
        self.name = name
        self.enabled = enabled
        self.otp = otp
        self.creation_date = datetime.utcnow() if creation_date is None else creation_date
        self.id = id

    @classmethod
    def by_id(cls, id):
        if not isinstance(id, uuid.UUID):
            id = uuid.UUID(id)
        return DBSession.query(cls).filter(cls.id == id).first()

    @classmethod
    def by_code(cls, code):
        if code is None:
            return None
        if not isinstance(code, int):
            code = int(code)
        return DBSession.query(cls).filter(cls.code == code).first()

    @classmethod
    def list(cls):
        return DBSession.query(cls).order_by(cls.name).all()

    @classmethod
    def enabled_list(cls):
        return DBSession.query(cls).filter(cls.enabled == True).order_by(cls.name).all()

    @classmethod
    def create(cls):
        client_code = random.randint(1000, 9999)
        otp = random.randint(1000, 9999)
        name = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(6))
        client = Client(client_code, name, False, otp)
        DBSession.add(client)
        return client


user_group = Table(
    'auth_usergroups', Base.metadata,
    Column('UserGroupID', GUID(), primary_key=True, default=uuid.uuid4),
    Column('UserID', GUID(), ForeignKey('auth_users.UserID')),
    Column('GroupID', GUID(), ForeignKey('auth_groups.GroupID'))
)

role_group = Table(
    'auth_rolegroups', Base.metadata,
    Column('RoleGroupID', GUID(), primary_key=True, default=uuid.uuid4),
    Column('RoleID', GUID(), ForeignKey('auth_roles.RoleID')),
    Column('GroupID', GUID(), ForeignKey('auth_groups.GroupID'))
)


class User(Base):
    __tablename__ = 'auth_users'

    id = Column('UserID', GUID(), primary_key=True, default=uuid.uuid4)
    name = Column('Name', Unicode(255), unique=True)
    _password = Column('Password', Unicode(60))
    locked_out = Column('LockedOut', Boolean)

    groups = relationship("Group", secondary=user_group)
    login_history = relationship('LoginHistory', backref='user')

    def _get_password(self):
        return self._password

    def _set_password(self, password):
        self._password = encrypt(password)

    password = property(_get_password, _set_password)
    password = synonym('_password', descriptor=password)


    @property
    def __name__(self):
        return self.name

    def __init__(self, name=None, password=None, locked_out=None, id=None):
        self.name = name
        self.password = password
        self.locked_out = locked_out
        self.id = id

    @classmethod
    def by_name(cls, name):
        if not name:
            return None
        return DBSession.query(cls).filter(cls.name.ilike(name)).first()

    @classmethod
    def by_id(cls, id):
        if not isinstance(id, uuid.UUID):
            id = uuid.UUID(id)
        return DBSession.query(cls).filter(cls.id == id).one()

    @classmethod
    def auth(cls, name, password):
        if password is None:
            return False, None
        user = cls.by_name(name)
        if not user:
            return False, None
        if user.password != encrypt(password) or user.locked_out:
            return False, None
        else:
            return True, user

    @classmethod
    def list(cls):
        return DBSession.query(cls).order_by(cls.name).all()

    @classmethod
    def query(cls):
        return DBSession.query(cls)

    @classmethod
    def filtered_list(cls, name):
        query = DBSession.query(cls)
        for item in name.split():
            query = query.filter(cls.name.ilike('%' + item + '%'))
        return query.order_by(cls.name)


class LoginHistory(Base):
    __tablename__ = 'auth_login_history'
    __table_args__ = (UniqueConstraint('user_id', 'client_id', 'date'), )
    id = Column('login_history_id', GUID(), primary_key=True, default=uuid.uuid4)
    user_id = Column('user_id', GUID(), ForeignKey('auth_users.UserID'), nullable=False)
    client_id = Column('client_id', GUID(), ForeignKey('auth_clients.client_id'), nullable=False)
    date = Column('date', DateTime(timezone=True), nullable=False)

    def __init__(self, user_id=None, client_id=None, date=None, id=None):
        self.user_id = user_id
        self.client_id = client_id
        self.date = datetime.utcnow() if date is None else date
        self.id = id


class Group(Base):
    __tablename__ = 'auth_groups'

    id = Column('GroupID', GUID(), primary_key=True, default=uuid.uuid4)
    name = Column('Name', Unicode(255), unique=True)


    def __init__(self, name=None, id=None):
        self.name = name
        self.id = id

    @classmethod
    def by_id(cls, id):
        return DBSession.query(cls).filter(cls.id == id).one()


class Role(Base):
    __tablename__ = 'auth_roles'

    id = Column('RoleID', GUID(), primary_key=True, default=uuid.uuid4)
    name = Column('Name', Unicode(255), unique=True)

    groups = relationship("Group", secondary=role_group, backref="roles")

    def __init__(self, name=None, id=None):
        self.name = name
        self.id = id

    @classmethod
    def list(cls):
        return DBSession.query(cls).order_by(cls.name).all()

    @classmethod
    def by_id(cls, id):
        return DBSession.query(cls).filter(cls.id == id).one()

