import datetime
import uuid

import jwt
from django.conf import settings
from django.db import models
from django.db.models import Q
from django.utils import timezone
from django.utils.timezone import now

from authenticate import conf as authenticate_conf
from plan.utils import find_best_max_institutes_per_user_in_plans, find_best_min_wage_fee_in_plans, \
    find_best_tax_factor_in_plans, find_best_wage_factor_in_plans
from utils.utils import logs_adder
from wikiazma.storage_helper import public_storage


class User(models.Model):
    """
    This class is responsible for creating User table in database
    all attributes and methods of User should defines in this class
    """
    id = models.UUIDField(primary_key=True, unique=True,
                          db_index=True, default=uuid.uuid4, editable=False)
    # The UUIDField itself will not generate the UUID for you, so it is recommended to use a default
    # so we assign an instance of uuid4 of python uuid package to default attribute which returns a random uuid for us
    first_name = models.CharField(max_length=46, null=True, blank=True)
    last_name = models.CharField(max_length=46, null=True, blank=True)
    username = models.CharField(max_length=254, unique=True,
                                db_index=True, null=True, blank=False)
    email = models.CharField(max_length=254, unique=True,
                             db_index=True, null=True, blank=True)
    phone = models.CharField(max_length=15, unique=True,
                             db_index=True, null=True, blank=True)
    # we have added it to support those institutes that are not willing to disclose their student's phone numbers to us.
    # We use the institute's phone number as recovery_phone for their students (recovery_phone is not unique)
    recovery_phone = models.CharField(max_length=15, unique=False,
                                      db_index=True, null=True, blank=True)
    image = models.ImageField(upload_to='users/profile/images', blank=True, null=True, storage=public_storage)
    image_thumbnail = models.ImageField(upload_to='users/profile/image_thumbnails',
                                        blank=True, null=True, storage=public_storage)
    poster = models.ImageField(upload_to='users/posters', blank=True, null=True, storage=public_storage)
    poster_thumbnail = models.ImageField(upload_to='users/posters_thumbnails',
                                         blank=True, null=True, storage=public_storage)
    # JSONField uses jsonb by default
    info = models.JSONField(null=True, blank=True)
    password = models.CharField(max_length=256, null=True, blank=True)
    g_subject = models.CharField(
        max_length=120, unique=True, db_index=True, null=True, blank=True)
    fb_id = models.CharField(max_length=120, unique=True,
                             db_index=True, null=True, blank=True)
    join_date = models.DateTimeField(auto_now_add=True, db_index=True)
    modified_at = models.DateTimeField(auto_now=True, db_index=True)

    def __str__(self):
        return f'{self.full_name} - {self.phone if self.phone else self.email}'

    @property
    def full_name(self):
        if self.first_name and self.last_name:
            return f"{self.first_name} {self.last_name}"
        elif self.first_name:
            return self.first_name
        elif self.last_name:
            return self.last_name
        else:
            return "-"

    @property
    def balance(self):
        from payment.models import UserBalanceHistory
        return UserBalanceHistory.get_last_user_balance(self).remaining_balance

    _plans = None
    _active_environment = None

    @property
    def active_environment(self):
        if not self._active_environment:
            from plan.models import Environment
            self._active_environment, _ = Environment.objects.get_or_create(active_env=True)
        return self._active_environment

    @property
    def plans(self):
        if not self._plans:
            from plan.models import UserPlan
            self._plans = UserPlan.objects.filter(Q(end_date__gte=timezone.now()) | Q(end_date__isnull=True), user=self,
                                                  start_date__lte=timezone.now())
        return self._plans

    @property
    def best_max_institutes_per_user(self):
        return find_best_max_institutes_per_user_in_plans(self.plans, self.active_environment)

    @property
    def best_min_wage_fee(self):
        return find_best_min_wage_fee_in_plans(self.plans, self.active_environment)

    @property
    def best_wage_factor(self):
        return find_best_wage_factor_in_plans(self.plans, self.active_environment)

    @property
    def best_tax_factor(self):
        return find_best_tax_factor_in_plans(self.plans, self.active_environment)


class VerificationAttempt(models.Model):
    id = models.BigAutoField(primary_key=True, unique=True, db_index=True)
    ip = models.CharField(max_length=45, db_index=True, null=True, blank=True)
    email = models.EmailField(max_length=254, db_index=True, null=True, blank=True)
    phone = models.CharField(max_length=15, db_index=True, null=True, blank=True)
    # username verification attempt is done using recovery_phone
    username = models.EmailField(max_length=254, db_index=True, null=True, blank=False)
    recovery_phone = models.CharField(max_length=15, db_index=True, null=True, blank=True)
    verification_code = models.CharField(max_length=6)
    created_at = models.DateTimeField(default=now, db_index=True)
    modified_at = models.DateTimeField(default=now, db_index=True)

    @staticmethod
    def cleanup_expired_verification_attempts():
        verification_attempts = VerificationAttempt.objects.filter(created_at__lt=timezone.now(
        ) - datetime.timedelta(hours=authenticate_conf.temp_user_cleanup_offset_hours))
        verification_attempts_count = verification_attempts.count()
        verification_attempts.delete()
        logs_adder(
            f'Event: SCHEDULE, cleanup, VerificationAttempt, {verification_attempts_count}')

    def __str__(self):
        return f'{self.email} - {self.phone} - {self.ip}'


class WrongVerificationAttempt(models.Model):
    ip = models.CharField(max_length=45, db_index=True, null=True, blank=True)
    email = models.EmailField(max_length=254, db_index=True, null=True, blank=True)
    phone = models.CharField(max_length=15, null=True, blank=True)
    # username verification attempt is done using recovery_phone
    username = models.EmailField(max_length=254, db_index=True, null=True, blank=False)
    # we do not need recovery_phone here
    # recovery_phone = models.CharField(max_length=15, db_index=True, null=True, blank=True)
    created_at = models.DateTimeField(default=now, db_index=True)
    modified_at = models.DateTimeField(default=now, db_index=True)


class WrongPasswordAttempt(models.Model):
    """
    This class is responsible for creating WrongPasswordAttempt table in database
    all attributes and methods of WrongPasswordAttempt should defines in this class
    """
    id = models.BigAutoField(primary_key=True, unique=True, db_index=True)
    ip = models.CharField(max_length=45, db_index=True)
    username = models.EmailField(max_length=254, db_index=True, null=True, blank=False)
    email = models.EmailField(max_length=254, db_index=True, null=True, blank=True)
    phone = models.CharField(max_length=15, null=True, blank=True)
    created_at = models.DateTimeField(default=now, db_index=True)
    modified_at = models.DateTimeField(default=now, db_index=True)

    @staticmethod
    def cleanup_expired_wrong_password_attempt():
        wrong_password_attempts = WrongPasswordAttempt.objects.filter(created_at__lt=timezone.now(
        ) - datetime.timedelta(hours=authenticate_conf.wrong_password_attempt_cleanup_offset_hours))
        wrong_password_attempts_count = wrong_password_attempts.count()
        wrong_password_attempts.delete()
        logs_adder(
            f'Event: SCHEDULE, cleanup, wrong password attempt, {wrong_password_attempts_count}')

    def __str__(self):
        return f'{self.email} - {self.phone}'


class UserJWT(models.Model):
    """
    This class is responsible for creating JWToken table in database
    all attributes and methods of JWToken should defines in this class
    """
    id = models.BigAutoField(primary_key=True, unique=True, db_index=True)
    user = models.ForeignKey(User, on_delete=models.CASCADE, db_index=True)
    token = models.CharField(max_length=460, unique=True, db_index=True)
    created_at = models.DateTimeField(auto_now_add=True, db_index=True)
    modified_at = models.DateTimeField(auto_now=True, db_index=True)
    expire_at = models.DateTimeField()

    def __str__(self):
        return self.token[-10:]

    @staticmethod
    def create_jwt(user):
        """
        This function used for creating a jwt token and returns it
        """
        return jwt.encode(
            {'exp': datetime.datetime.now() + datetime.timedelta(days=authenticate_conf.user_jwt_expire_time_in_days),
             'iss': settings.JWT_ISSUER, 'aud': settings.JWT_AUDIENCE, 'user_id': str(user.id),
             'random_key': uuid.uuid4().hex}, settings.SECRET_KEY, algorithm='HS256')

    @staticmethod
    def create_record(user):
        """
        This function used for creating a jwt record in database. it takes a user object
        as its argument and create a jwt record for that user in database.
        """
        token = UserJWT.create_jwt(user)
        UserJWT.objects.create(
            user=user,
            token=token,
            expire_at=timezone.now() +
                      datetime.timedelta(
                          days=authenticate_conf.user_jwt_expire_time_in_days),
        )
        return token

    @staticmethod
    def cleanup_expired_userjwts():
        jwts = UserJWT.objects.filter(expire_at__lt=timezone.now())
        jwts_count = jwts.count()
        jwts.delete()
        logs_adder(f'Event: SCHEDULE, cleanup, UserJWTs, {jwts_count}')


class SMSCall(models.Model):
    phone = models.CharField(max_length=15, db_index=True)
    message = models.TextField()
    url = models.TextField()
    header = models.TextField(null=True, blank=True)
    post_data = models.TextField(null=True, blank=True)
    pattern_code = models.CharField(
        max_length=40, db_index=True, null=True, blank=True)
    response = models.TextField(null=True, blank=True)
    success = models.BooleanField(db_index=True)
    created_at = models.DateTimeField(auto_now_add=True, db_index=True)
    modified_at = models.DateTimeField(auto_now=True, db_index=True)

    @staticmethod
    def cleanup_expired_sms_calls():
        sms_calls = SMSCall.objects.filter(created_at__lt=timezone.now(
        ) - datetime.timedelta(hours=authenticate_conf.sms_calls_cleanup_offset_hours))
        sms_calls_count = sms_calls.count()
        sms_calls.delete()
        logs_adder(f'Event: SCHEDULE, cleanup, SMS Calls, {sms_calls_count}')

    def __str__(self):
        return self.phone


class EmailCall(models.Model):
    id = models.BigAutoField(primary_key=True, unique=True, db_index=True)
    email = models.CharField(max_length=254, db_index=True)
    message = models.TextField()
    url = models.TextField(null=True, blank=True)
    header = models.TextField(null=True, blank=True)
    post_data = models.TextField(null=True, blank=True)
    response = models.TextField(null=True, blank=True)
    success = models.BooleanField(db_index=True)
    created_at = models.DateTimeField(auto_now_add=True, db_index=True)
    modified_at = models.DateTimeField(auto_now=True, db_index=True)

    @staticmethod
    def cleanup_expired_email_calls():
        email_calls = EmailCall.objects.filter(created_at__lt=timezone.now(
        ) - datetime.timedelta(hours=authenticate_conf.email_calls_cleanup_offset_hours))
        email_calls_count = email_calls.count()
        email_calls.delete()
        logs_adder(
            f'Event: SCHEDULE, cleanup, Email Calls, {email_calls_count}')

    def __str__(self):
        return self.email
