from app import db
from flask_login import UserMixin
import datetime
from sqlalchemy import Column, Integer, String, DateTime, Text


# from cryptography.fernet import Fernet
# from app.config import Config


class User(db.Model, UserMixin):
    __tablename__ = 'user'

    id = Column(Integer, primary_key=True)
    username = Column(String(120), unique=True)
    email = Column(String(120), unique=True)
    password = Column(String(120), nullable=True)
    last_login = Column(db.DateTime, nullable=True)
    date_created = db.Column(db.DateTime, default=datetime.datetime.now)

    api_keys_created = db.relationship("APIKey")

    groups_created = db.relationship("Group")

    logs = db.relationship("UserLog")
    roles = db.relationship('Role', secondary='user_roles')
    groups = db.relationship('Group', secondary='group_users')

    def __init__(self, **kwargs):
        for property, value in kwargs.items():
            # depending on whether value is an iterable or not, we must
            # unpack it's value (when **kwargs is request.form, some values
            # will be a 1-element list)
            if hasattr(value, '__iter__') and not isinstance(value, str):
                # the ,= unpack of a singleton fails PEP8 (travis flake8 test)
                value = value[0]
            setattr(self, property, value)

    def __repr__(self):
        return f'User({self.id}, {self.username})'

    @classmethod
    def add_relationship_one_to_many(cls, reference_name, object):
        """Allows plugins to inject one to many relationships to the user at runtime"""
        if type(reference_name) is not str:
            raise TypeError('reference_name argument MUST be a string!')
        if not issubclass(object, db.Model):
            raise TypeError('object MUST inherit from db.Model!')
        cls.__mapper__.add_property(reference_name, db.relationship(object))

    def has_role(self, *args):
        '''Allows Jinja to check to see if a current user has a particular role

        :param args: roles
        :return:
        '''
        # Gets all of the associated roles a user has access to via their assigned groups
        group_associated_roles = []
        for group in self.groups:
            for role in group.roles:
                group_associated_roles.append(role)

        # List containing all of the roles a user is associated with
        all_associated_roles = group_associated_roles + self.roles

        return bool(set(args) & {role.name for role in all_associated_roles})


# Define the Role data-model
class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(50), unique=True)

    def __repr__(self):
        return 'Role({}, {})'.format(self.id, self.name)


# Define the Group data-model
class Group(db.Model):
    __tablename__ = 'group'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(50), unique=True)
    date_created = db.Column(db.DateTime, default=datetime.datetime.now)

    # creates bidirectional relationship to track who created each group
    user_id_created_by = Column(db.Integer, db.ForeignKey('user.id'))
    # created_by = db.relationship("User", uselist=False, back_populates="groups_created")

    roles = db.relationship('Role', secondary='group_roles')
    users = db.relationship('User', secondary='group_users')

    # allows to get a user object from the  user_id_created_by column vs just the ID
    @property
    def created_by(self):
        return User.query.filter_by(id=self.user_id_created_by).first()

    # allows to set the user_id_created_by relationship column by passing the user object
    @created_by.setter
    def created_by(self, user_object):
        self.user_id_created_by = user_object.id

    def __repr__(self):
        return 'Group({}, {})'.format(self.id, self.name)


class UserLog(db.Model):
    __tablename__ = 'user_log'
    id = db.Column(db.Integer(), primary_key=True)
    message = db.Column(db.String(200), nullable=False)
    timestamp = db.Column(db.DateTime, default=datetime.datetime.now)
    user = db.Column(Integer, db.ForeignKey('user.id'))


# Define the UserRoles association table
class UserRoles(db.Model):
    __tablename__ = 'user_roles'
    id = db.Column(db.Integer(), primary_key=True)
    user_id = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'))
    role_id = db.Column(db.Integer(), db.ForeignKey('roles.id', ondelete='CASCADE'))


# Define the GroupRoles association table
class GroupRoles(db.Model):
    __tablename__ = 'group_roles'
    id = db.Column(db.Integer(), primary_key=True)
    group_id = db.Column(db.Integer(), db.ForeignKey('group.id', ondelete='CASCADE'))
    role_id = db.Column(db.Integer(), db.ForeignKey('roles.id', ondelete='CASCADE'))


# Define the GroupUsers association table
class GroupUsers(db.Model):
    __tablename__ = 'group_users'
    id = db.Column(db.Integer(), primary_key=True)
    group_id = db.Column(db.Integer(), db.ForeignKey('group.id', ondelete='CASCADE'))
    user_id = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'))


class APIKey(db.Model):
    __tablename__ = 'api_key'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(120), nullable=False)
    description = db.Column(db.Text, nullable=True)
    key = Column(String(240), nullable=False)
    date_created = Column(db.DateTime, default=datetime.datetime.now)
    date_last_used = Column(db.DateTime, nullable=True)

    created_by_user_id = Column(db.Integer, db.ForeignKey('user.id'))

    # allows to get a user object from the  user_id_created_by column vs just the ID
    @property
    def created_by_user(self):
        return User.query.filter_by(id=self.user_id_created_by).first()

    # allows to set the user_id_created_by relationship column by passing the user object
    @created_by_user.setter
    def created_by_user(self, user_object):
        self.created_by_user_id = user_object.id

    def __repr__(self):
        return f'APIKey({self.id}, {self.name})'


class Log(db.Model):
    __tablename__ = 'log'
    id = db.Column(db.Integer(), primary_key=True)
    message = db.Column(db.String(200), nullable=False)
    timestamp = db.Column(db.DateTime, default=datetime.datetime.now)
    user = db.Column(Integer, nullable=True)
    log_type = db.Column(Integer, nullable=True)


# class ServiceAccount(db.Model):
#     __tablename__ = 'service_account'
#     id = db.Column(db.Integer(), primary_key=True)
#     username = db.Column(db.String(256), nullable=False)
#     _password = db.Column(db.Text(), nullable=False, name='password')
#
#     @property
#     def password(self):
#         encryptor = Fernet(Config.SECRET_KEY.encode())
#         decrypted_password = encryptor.decrypt(self._password.encode())
#         decoded_password = decrypted_password.decode()
#         return decoded_password
#
#     @password.setter
#     def password(self, value):
#         encryptor = Fernet(Config.SECRET_KEY.encode())
#         encrypted_password = encryptor.encrypt(value.encode())
#         decoded_encrypted_string = encrypted_password.decode()
#         self._password = decoded_encrypted_string


class IntervalSchedulerJob(db.Model):
    __tablename__ = 'interval_scheduler_job'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(120), nullable=False)
    enabled = db.Column(db.Boolean, nullable=False, default=False)
    interval_minutes = db.Column(db.Integer(), nullable=True)

    def enable_job(self):
        """Does not enable the job in the scheduler, only sets it to be enabled in the database.
        Reason we are not doing it here is if we import the scheduler it creates cascading circular import errors.
        """
        if self.interval_minutes:
            self.enabled = True
        db.session.commit()


class CronSchedulerJob(db.Model):
    __tablename__ = 'cron_scheduler_job'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(120), nullable=False)
    enabled = db.Column(db.Boolean, nullable=False, default=False)

    _year = db.Column(db.Integer(), nullable=True, default=None, name='year')
    _month = db.Column(db.Integer(), nullable=True, default=None, name='month')
    _day = db.Column(db.Integer(), nullable=True, default=None, name='day')
    _hour = db.Column(db.Integer(), nullable=True, default=None, name='hour')
    _minute = db.Column(db.Integer(), nullable=True, default=None, name='minute')
    _second = db.Column(db.Integer(), nullable=True, default=None, name='second')
    _day_of_week = db.Column(db.Integer(), nullable=True, default=None, name='day_of_week')

    @property
    def year(self):
        if self._year is None:
            return '*'

        return self._year

    @property
    def month(self):
        if self._month is None:
            return '*'

        return self._month

    @property
    def day(self):
        if self._day is None:
            return '*'

        return self._day

    @property
    def hour(self):
        if self._hour is None:
            return '*'

        return self._hour

    @property
    def minute(self):
        if self._minute is None:
            return '*'

        return self._minute

    @property
    def second(self):
        if self._second is None:
            return '*'

        return self._second

    @property
    def day_of_week(self):
        if self._day_of_week is None:
            return '*'

        return self._day_of_week



from app import db
from flask_login import UserMixin
import datetime
from sqlalchemy import Column, Integer, String, DateTime, Text


# from cryptography.fernet import Fernet
# from app.config import Config


class User(db.Model, UserMixin):
    __tablename__ = 'user'

    id = Column(Integer, primary_key=True)
    username = Column(String(120), unique=True)
    email = Column(String(120), unique=True)
    password = Column(String(120), nullable=True)
    last_login = Column(db.DateTime, nullable=True)
    date_created = db.Column(db.DateTime, default=datetime.datetime.now)

    api_keys_created = db.relationship("APIKey")

    groups_created = db.relationship("Group")

    logs = db.relationship("UserLog")
    roles = db.relationship('Role', secondary='user_roles')
    groups = db.relationship('Group', secondary='group_users')

    def __init__(self, **kwargs):
        for property, value in kwargs.items():
            # depending on whether value is an iterable or not, we must
            # unpack it's value (when **kwargs is request.form, some values
            # will be a 1-element list)
            if hasattr(value, '__iter__') and not isinstance(value, str):
                # the ,= unpack of a singleton fails PEP8 (travis flake8 test)
                value = value[0]
            setattr(self, property, value)

    def __repr__(self):
        return f'User({self.id}, {self.username})'

    @classmethod
    def add_relationship_one_to_many(cls, reference_name, object):
        """Allows plugins to inject one to many relationships to the user at runtime"""
        if type(reference_name) is not str:
            raise TypeError('reference_name argument MUST be a string!')
        if not issubclass(object, db.Model):
            raise TypeError('object MUST inherit from db.Model!')
        cls.__mapper__.add_property(reference_name, db.relationship(object))

    def has_role(self, *args):
        '''Allows Jinja to check to see if a current user has a particular role

        :param args: roles
        :return:
        '''
        # Gets all of the associated roles a user has access to via their assigned groups
        group_associated_roles = []
        for group in self.groups:
            for role in group.roles:
                group_associated_roles.append(role)

        # List containing all of the roles a user is associated with
        all_associated_roles = group_associated_roles + self.roles

        return bool(set(args) & {role.name for role in all_associated_roles})


# Define the Role data-model
class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(50), unique=True)

    def __repr__(self):
        return 'Role({}, {})'.format(self.id, self.name)


# Define the Group data-model
class Group(db.Model):
    __tablename__ = 'group'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(50), unique=True)
    date_created = db.Column(db.DateTime, default=datetime.datetime.now)

    # creates bidirectional relationship to track who created each group
    user_id_created_by = Column(db.Integer, db.ForeignKey('user.id'))
    # created_by = db.relationship("User", uselist=False, back_populates="groups_created")

    roles = db.relationship('Role', secondary='group_roles')
    users = db.relationship('User', secondary='group_users')

    # allows to get a user object from the  user_id_created_by column vs just the ID
    @property
    def created_by(self):
        return User.query.filter_by(id=self.user_id_created_by).first()

    # allows to set the user_id_created_by relationship column by passing the user object
    @created_by.setter
    def created_by(self, user_object):
        self.user_id_created_by = user_object.id

    def __repr__(self):
        return 'Group({}, {})'.format(self.id, self.name)


class UserLog(db.Model):
    __tablename__ = 'user_log'
    id = db.Column(db.Integer(), primary_key=True)
    message = db.Column(db.String(200), nullable=False)
    timestamp = db.Column(db.DateTime, default=datetime.datetime.now)
    user = db.Column(Integer, db.ForeignKey('user.id'))


# Define the UserRoles association table
class UserRoles(db.Model):
    __tablename__ = 'user_roles'
    id = db.Column(db.Integer(), primary_key=True)
    user_id = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'))
    role_id = db.Column(db.Integer(), db.ForeignKey('roles.id', ondelete='CASCADE'))


# Define the GroupRoles association table
class GroupRoles(db.Model):
    __tablename__ = 'group_roles'
    id = db.Column(db.Integer(), primary_key=True)
    group_id = db.Column(db.Integer(), db.ForeignKey('group.id', ondelete='CASCADE'))
    role_id = db.Column(db.Integer(), db.ForeignKey('roles.id', ondelete='CASCADE'))


# Define the GroupUsers association table
class GroupUsers(db.Model):
    __tablename__ = 'group_users'
    id = db.Column(db.Integer(), primary_key=True)
    group_id = db.Column(db.Integer(), db.ForeignKey('group.id', ondelete='CASCADE'))
    user_id = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'))


class APIKey(db.Model):
    __tablename__ = 'api_key'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(120), nullable=False)
    description = db.Column(db.Text, nullable=True)
    key = Column(String(240), nullable=False)
    date_created = Column(db.DateTime, default=datetime.datetime.now)
    date_last_used = Column(db.DateTime, nullable=True)

    created_by_user_id = Column(db.Integer, db.ForeignKey('user.id'))

    # allows to get a user object from the  user_id_created_by column vs just the ID
    @property
    def created_by_user(self):
        return User.query.filter_by(id=self.user_id_created_by).first()

    # allows to set the user_id_created_by relationship column by passing the user object
    @created_by_user.setter
    def created_by_user(self, user_object):
        self.created_by_user_id = user_object.id

    def __repr__(self):
        return f'APIKey({self.id}, {self.name})'


class Log(db.Model):
    __tablename__ = 'log'
    id = db.Column(db.Integer(), primary_key=True)
    message = db.Column(db.String(200), nullable=False)
    timestamp = db.Column(db.DateTime, default=datetime.datetime.now)
    user = db.Column(Integer, nullable=True)
    log_type = db.Column(Integer, nullable=True)


# class ServiceAccount(db.Model):
#     __tablename__ = 'service_account'
#     id = db.Column(db.Integer(), primary_key=True)
#     username = db.Column(db.String(256), nullable=False)
#     _password = db.Column(db.Text(), nullable=False, name='password')
#
#     @property
#     def password(self):
#         encryptor = Fernet(Config.SECRET_KEY.encode())
#         decrypted_password = encryptor.decrypt(self._password.encode())
#         decoded_password = decrypted_password.decode()
#         return decoded_password
#
#     @password.setter
#     def password(self, value):
#         encryptor = Fernet(Config.SECRET_KEY.encode())
#         encrypted_password = encryptor.encrypt(value.encode())
#         decoded_encrypted_string = encrypted_password.decode()
#         self._password = decoded_encrypted_string


class IntervalSchedulerJob(db.Model):
    __tablename__ = 'interval_scheduler_job'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(120), nullable=False)
    enabled = db.Column(db.Boolean, nullable=False, default=False)
    interval_minutes = db.Column(db.Integer(), nullable=True)

    def enable_job(self):
        """Does not enable the job in the scheduler, only sets it to be enabled in the database.
        Reason we are not doing it here is if we import the scheduler it creates cascading circular import errors.
        """
        if self.interval_minutes:
            self.enabled = True
        db.session.commit()


class CronSchedulerJob(db.Model):
    __tablename__ = 'cron_scheduler_job'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(120), nullable=False)
    enabled = db.Column(db.Boolean, nullable=False, default=False)

    _year = db.Column(db.Integer(), nullable=True, default=None, name='year')
    _month = db.Column(db.Integer(), nullable=True, default=None, name='month')
    _day = db.Column(db.Integer(), nullable=True, default=None, name='day')
    _hour = db.Column(db.Integer(), nullable=True, default=None, name='hour')
    _minute = db.Column(db.Integer(), nullable=True, default=None, name='minute')
    _second = db.Column(db.Integer(), nullable=True, default=None, name='second')
    _day_of_week = db.Column(db.Integer(), nullable=True, default=None, name='day_of_week')

    @property
    def year(self):
        if self._year is None:
            return '*'

        return self._year

    @property
    def month(self):
        if self._month is None:
            return '*'

        return self._month

    @property
    def day(self):
        if self._day is None:
            return '*'

        return self._day

    @property
    def hour(self):
        if self._hour is None:
            return '*'

        return self._hour

    @property
    def minute(self):
        if self._minute is None:
            return '*'

        return self._minute

    @property
    def second(self):
        if self._second is None:
            return '*'

        return self._second

    @property
    def day_of_week(self):
        if self._day_of_week is None:
            return '*'

        return self._day_of_week



# from cryptography.fernet import Fernet # from app.config import Config # depending on whether value is an iterable or not, we must # unpack it's value (when **kwargs is request.form, some values # will be a 1-element list) # the ,= unpack of a singleton fails PEP8 (travis flake8 test) Allows plugins to inject one to many relationships to the user at runtime Allows Jinja to check to see if a current user has a particular role          :param args: roles         :return: # Gets all of the associated roles a user has access to via their assigned groups # List containing all of the roles a user is associated with # Define the Role data-model # Define the Group data-model # creates bidirectional relationship to track who created each group # created_by = db.relationship("User", uselist=False, back_populates="groups_created") # allows to get a user object from the  user_id_created_by column vs just the ID # allows to set the user_id_created_by relationship column by passing the user object # Define the UserRoles association table # Define the GroupRoles association table # Define the GroupUsers association table # allows to get a user object from the  user_id_created_by column vs just the ID # allows to set the user_id_created_by relationship column by passing the user object # class ServiceAccount(db.Model): #     __tablename__ = 'service_account' #     id = db.Column(db.Integer(), primary_key=True) #     username = db.Column(db.String(256), nullable=False) #     _password = db.Column(db.Text(), nullable=False, name='password') # #     @property #     def password(self): #         encryptor = Fernet(Config.SECRET_KEY.encode()) #         decrypted_password = encryptor.decrypt(self._password.encode()) #         decoded_password = decrypted_password.decode() #         return decoded_password # #     @password.setter #     def password(self, value): #         encryptor = Fernet(Config.SECRET_KEY.encode()) #         encrypted_password = encryptor.encrypt(value.encode()) #         decoded_encrypted_string = encrypted_password.decode() #         self._password = decoded_encrypted_string Does not enable the job in the scheduler, only sets it to be enabled in the database.         Reason we are not doing it here is if we import the scheduler it creates cascading circular import errors.