# import packages from default or pip library
from datetime import datetime, timezone
from mongoengine import Document, BooleanField, DateTimeField, IntField, StringField

# import packages from this framework
from settings import PASSWORD_POLICIES
from ..rdbms.users import encrypt_password


# password encryption algorithm
ENCRYPT_TYPE: str = PASSWORD_POLICIES.get("encrypt_type")

# define Class for Common SQLModel
class LastSigninDateTimeMixin(Document):
    meta = {'abstract': True}

    last_signin_dt: datetime = DateTimeField(null=True, default=None)

    def __setattr__(self, name: str, value: datetime):
        if name == "last_signin_dt":
            if hasattr(self, name) and getattr(self, name) is not None:
                raise ValueError(f"[Warning] it is not allowed to edit value of column '{name}' in '{self.__class__}' directly.")

        super().__setattr__(name, value)
        return None

    def update_signin_dt(self):
        super().__setattr__(name="last_signin_dt", value=datetime.now(tz=timezone.utc))
        return None


class SigninFailMixin(Document):
    meta = {'abstract': True}

    is_active: bool = BooleanField(null=False, default=False, db_field="is_active")
    signin_fail: int = IntField(null=False, default=0, ge=0, le=5, db_field="signin_fail")

    def activate_user(self) -> None:
        super().__setattr__(name="is_active", value=True)
        self.__init_signin_fail_count()
        return None

    def deactivate_user(self) -> None:
        super().__setattr__(name="is_active", value=False)
        return None

    def add_signin_fail_count(self, limit: int) -> None:
        if self.signin_fail < limit:
            super().__setattr__(name="signin_fail", value=self.signin_fail + 1)

            if self.signin_fail == limit:
                self.deactivate_user()

        return None

    def __init_signin_fail_count(self) -> None:
        super().__setattr__(name="signin_fail", value=0)
        return None


class IsAdminMixin(Document):
    meta = {'abstract': True}

    is_admin: bool = BooleanField(null=False, default=False, db_field="is_admin")

    def grant_admin(self) -> None:
        super().__setattr__(name="is_admin", value=True)
        return None

    def revoke_admin(self) -> None:
        super().__setattr__(name="is_admin", value=False)
        return None


class PasswordMixin(Document):
    meta = {'abstract': True}

    password: str = StringField(null=False, unique=False)

    def __setattr__(self, name, value) -> None:
        if name == "password":
            if hasattr(self, name) and getattr(self, name) is not None:
                raise ValueError(f"[Warning] it is not allowed to edit value of column '{name}' in '{self.__class__}' directly.")

        super().__setattr__(name, value)
        return None

    def check_password(self, password: str) -> bool:
        return self.password == encrypt_password(password=password)

    def change_password(self, old_pwd: str, new_pwd: str) -> bool:
        if self.password != encrypt_password(password=old_pwd):
            return False

        super().__setattr__(name="password", value=encrypt_password(password=new_pwd))
        return True

    def encrypt_password(self, enc_type:str=ENCRYPT_TYPE) -> None:
        super().__setattr__(name="password", value=encrypt_password(password=self.password, enc_type=enc_type))
        return None


class UsernameMixin(Document):
    meta = {'abstract': True}

    username: str = StringField(null=False, unique=True)

    def __setattr__(self, name, value) -> None:
        if name == "username":
            if hasattr(self, name) and getattr(self, name) is not None:
                raise ValueError(f"[Warning] it is not allowed to edit value of column '{name}' in '{self.__class__}' directly.")

        super().__setattr__(name, value)
        return None


# define function to control MongoDB document event
# please follow the procedure below
# create event handler function with name starting with _.
# event handler function must have 3 args: sender, document, **kwargs)
# each db column can get from 'document'
# import pre_init or pre_save from 'mongoengine.signals' in your model file.
# pre_init.connect(EVNET_HANDLER_FUNC_NAME, sender=TABLE_CLASS_NAME)