from django.contrib.auth.hashers import make_password, check_password
from django.contrib.auth.models import AbstractUser as dj_AbstractUser
from django.db import models
from django.utils.timezone import now as dj_now
from django.utils.timezone import timedelta
from django.utils.translation import gettext_lazy as _

from users.definitions import VERIFICATION_CODE
from users.imports.db import BaseDBModel as Parent
from users.imports.db import (
    Sender, VerificationCodeGenerator,
    verification_code_lifetime)
from users.utils.t import UnicodePhoneNumberValidator
from .notification_log import NotificationLog
from .user_manager import UserManager
from .verification_code import VCode


class User(dj_AbstractUser, Parent):
    roles = models.ManyToManyField(
        verbose_name=_('Roles'),
        to='Role',
        blank=True,
    )
    username = models.CharField(
        verbose_name=_('Username'),
        max_length=255,
        unique=True,
        blank=False,
        null=False,
        db_index=True,
    )
    password = models.CharField(
        verbose_name=_("Password"),
        max_length=128,
        blank=False,
        null=False,
    )
    phone_number_validator = UnicodePhoneNumberValidator()
    phone_number = models.CharField(
        verbose_name=_('Phone Number'),
        max_length=255,
        blank=True,
        null=True,
        help_text=_('Sample 09123456789'),
        validators=[phone_number_validator],
    )
    phone_number_verified = models.BooleanField(
        verbose_name=_('Phone Number Verified'),
        blank=False,
        null=False,
        default=False,
        help_text=_("To verify your phone number, all you need to do is enter "
                    "the verification code sent to your phone number once on the platform.")
    )
    email_address = models.EmailField(
        verbose_name=_("Email Address"),
        blank=True,
        null=True,
    )

    email_address_verified = models.BooleanField(
        verbose_name=_('Email Address Verified'),
        blank=False,
        null=False,
        default=False,
        help_text=_("To verify your email address, all you need to do is enter "
                    "the verification code sent to your email address once on the platform.")
    )

    @property
    def account_verified(self):
        return self.phone_number_verified or self.email_address_verified

    first_name = None
    last_name = None
    email = None

    objects = UserManager()

    REQUIRED_FIELDS = []
    EMAIL_FIELD = None
    USERNAME_FIELD = "username"

    def get_full_name(self):
        return str(self.username)

    def get_short_name(self):
        return str(self.username)

    def email_user(self, subject, message, from_email=None, **kwargs):
        pass

    def send_email(self, **kwargs):
        rslt = Sender().email(email_address=self.email_address, **kwargs)
        NotificationLog.objects.create(
            user_id=self.id, platform='Email', status='Fail' if rslt is None else 'Uncertain', kwargs=kwargs)
        return rslt

    def send_sms(self, **kwargs):
        rslt = Sender().sms(phone_number=self.phone_number, **kwargs)
        NotificationLog.objects.create(
            user_id=self.id, platform='SMS', status='Fail' if rslt is None else 'Uncertain', kwargs=kwargs)
        return rslt

    def send_native(self, **kwargs):
        rslt = Sender().native(userid=self.pk, **kwargs)
        NotificationLog.objects.create(
            user_id=self.id, platform='Native', status='Fail' if rslt is None else 'Success', kwargs=kwargs)
        return rslt

    def send_notification(self, platforms: list | str, **kwargs):
        ret = {}
        if 'SMS' in platforms:
            rslt = self.send_sms(**kwargs)
            ret['SMS'] = rslt

        if 'Email' in platforms:
            rslt = self.send_email(**kwargs)
            ret['Email'] = rslt

        if 'Native' in platforms:
            rslt = self.send_native(**kwargs)
            ret['Native'] = rslt

        if '__all__' in platforms:
            rslt = self.send_sms(**kwargs)
            ret['SMS'] = rslt
            rslt = self.send_email(**kwargs)
            ret['Email'] = rslt
            rslt = self.send_native(**kwargs)
            ret['Native'] = rslt
        return ret

    def send_verification_code(self, platforms: list | str = '__all__'):
        obj = VCode.objects.filter(
            user_id=self.pk, expiry_datetime__gte=dj_now()).order_by('-created_at').last()

        if obj:
            return

        code = VerificationCodeGenerator().generate()
        VCode.objects.create(
            user_id=self.pk,
            expiry_datetime=dj_now() + timedelta(minutes=verification_code_lifetime),
            hashed_code=make_password(code),
        )
        self.send_notification(
            platforms=platforms, template_key=VERIFICATION_CODE, message=code)

    def authenticator(self, key, value):
        match key:
            case 'password':
                return check_password(value, self.password)
            case 'vcode':
                self_vcode = self.vcodes.filter(expiry_datetime__gte=dj_now()).order_by('-created_at').last()
                if self_vcode is None:
                    return False
                return check_password(value, self_vcode.hashed_code)
            case _:
                return False

    def change_password(self, new_password, **qualifications):
        authenticate = (
                self.authenticator('password', qualifications.get('password')) or
                self.authenticator('vcode', qualifications.get('vcode')))

        if not authenticate:
            return False, 'Submitted identification documents are not acceptable.'

        self.password = make_password(new_password)
        self.save()
        return True, ''

    def change_username(self, new_username, **qualifications):
        obj = User.objects.filter(username=new_username).first()
        if obj:
            if obj.id == self.id:
                return True, ''
            return False, 'You are not authorized to use this username.'

        authenticate = (
                self.authenticator('password', qualifications.get('password')) or
                self.authenticator('vcode', qualifications.get('vcode')))

        if not authenticate:
            return False, 'Submitted identification documents are not acceptable.'

        self.username = new_username
        self.save()
        return True, ''

    def change_phone_number(self, new_phone_number, **qualifications):
        obj = User.objects.filter(phone_number=new_phone_number).first()
        if obj:
            if obj.id == self.id:
                return True, ''
            return False, 'You are not authorized to use this phone_number.'

        authenticate = (
                self.authenticator('password', qualifications.get('password')) or
                self.authenticator('vcode', qualifications.get('vcode')))

        if not authenticate:
            return False, 'Submitted identification documents are not acceptable.'

        self.phone_number = new_phone_number
        self.phone_number_verified = False
        self.save()
        return True, ''

    def change_email_address(self, new_email_address, **qualifications):
        obj = User.objects.filter(email_address=new_email_address).first()
        if obj:
            if obj.id == self.id:
                return True, ''
            return False, 'You are not authorized to use this email_address.'

        authenticate = (
                self.authenticator('password', qualifications.get('password')) or
                self.authenticator('vcode', qualifications.get('vcode')))

        if not authenticate:
            return False, 'Submitted identification documents are not acceptable.'

        self.email_address = new_email_address
        self.email_address_verified = False
        self.save()
        return True, ''

    def verify_phone_number(self, vcode):
        self.phone_number_verified = self.authenticator('vcode', vcode)
        self.save()
        return self.phone_number_verified

    def verify_email_address(self, vcode):
        self.email_address_verified = self.authenticator('vcode', vcode)
        self.save()
        return self.email_address_verified

    def __str__(self):
        return f'{self.phone_number or self.email_address or self.username}'

    class Meta:
        verbose_name = _('User')
        verbose_name_plural = _('Users')
        indexes = [
            models.Index(fields=['username', 'password']),
        ]
        swappable = 'AUTH_USER_MODEL'
