import datetime
import uuid

import jwt
from django.conf import settings
from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.db.models import Q
from django.utils import timezone
from model_utils import FieldTracker

from authenticate.models import User
from institute import conf as institute_conf
from plan.utils import find_best_max_collaborators_per_institute, find_best_max_question_banks_per_institute, \
    find_best_max_questions_per_question_bank
from utils.utils import logs_adder
from wikiazma.storage_helper import public_storage


class Institute(models.Model):
    id = models.UUIDField(primary_key=True, unique=True,
                          db_index=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=100, db_index=True)
    description = models.TextField(max_length=3000, null=True, blank=True)
    user = models.ForeignKey(User, on_delete=models.CASCADE, db_index=True)
    logo = models.ImageField(upload_to='institute/logo',
                             null=True, blank=True, storage=public_storage)
    logo_thumbnail = models.ImageField(upload_to='institute/logo_thumbnails',
                                       null=True, blank=True, storage=public_storage)
    poster = models.ImageField(
        upload_to='institute/posters', null=True, blank=True, storage=public_storage)
    poster_thumbnail = models.ImageField(upload_to='institute/posters_thumbnails',
                                         null=True, blank=True, storage=public_storage)
    info = models.JSONField(null=True, blank=True)
    on_exam_finished_redirect_url = models.CharField(max_length=100, blank=True, null=True)
    sandbox = models.BooleanField(default=False)

    _subscribed_question_banks = ArrayField(
        models.UUIDField(), default=list)

    created_at = models.DateTimeField(auto_now_add=True, db_index=True)
    modified_at = models.DateTimeField(auto_now=True, db_index=True)

    tracker = FieldTracker(fields=['name'])

    def __str__(self):
        return self.name

    _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 InstitutePlan
            self._plans = InstitutePlan.objects.filter(Q(end_date__gte=timezone.now()) | Q(end_date__isnull=True), institute=self,
                                                       start_date__lte=timezone.now())
        return self._plans

    @property
    def best_max_collaborators_per_institute(self):
        if self.sandbox:
            return find_best_max_collaborators_per_institute(self.plans)
        else:
            return find_best_max_collaborators_per_institute(self.plans, self.active_environment)

    @property
    def best_max_question_banks_per_institute(self):
        # if self.sandbox:
        #     return find_best_max_question_banks_per_institute(self.plans)
        # else:
        return find_best_max_question_banks_per_institute(self.plans, self.active_environment)

    @property
    def best_max_questions_per_question_bank(self):
        # if self.sandbox:
        #     return find_best_max_questions_per_question_bank(self.plans)
        # else:
        return find_best_max_questions_per_question_bank(self.plans, self.active_environment)

    @property
    def balance(self):
        from payment.models import InstituteBalanceHistory
        return InstituteBalanceHistory.get_last_institute_balance(self).remaining_balance

    def question_bank_is_picked(self, qb_pk):
        if qb_pk in self._subscribed_question_banks:
            return True
        else:
            return False

    def can_read_question_bank(self, qb):
        if qb.institute == self or self.question_bank_is_picked(qb.pk) or (qb.public and qb.price == 0):
            return True
        else:
            return False

    def subscribe_qb(self, qb_pk):
        if qb_pk not in self._subscribed_question_banks:
            self._subscribed_question_banks.append(qb_pk)
            self.save()

    def unsubscribe_qb(self, qb_pk):
        qb_pk_uuid = uuid.UUID(qb_pk)
        if qb_pk_uuid in self._subscribed_question_banks:
            self._subscribed_question_banks.remove(qb_pk_uuid)
            self.save()


class TransferOwnershipHistory(models.Model):
    id = models.UUIDField(primary_key=True, unique=True,
                          db_index=True, default=uuid.uuid4, editable=False)
    institute = models.ForeignKey(
        Institute, on_delete=models.CASCADE, db_index=True)
    old_owner = models.ForeignKey(
        User, on_delete=models.CASCADE, related_name='old_owner', db_index=True)
    new_owner = models.ForeignKey(
        User, on_delete=models.CASCADE, related_name='new_owner', db_index=True)
    description = models.TextField(max_length=3000, null=True, blank=True)
    new_owner_accept_key = models.CharField(
        max_length=64, unique=True, null=True, blank=True)
    old_owner_accept_key = models.CharField(
        max_length=64, unique=True, null=True, blank=True)
    new_owner_accept_at = models.DateTimeField(
        db_index=True, null=True, blank=True)
    old_owner_accept_at = models.DateTimeField(
        db_index=True, null=True, blank=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_transfer_ownership():
        transfer_ownerships = TransferOwnershipHistory.objects.filter(
            Q(new_owner_accept_at__lt=timezone.now() - datetime.timedelta(
                days=institute_conf.transfer_ownership_cleanup_offset_days)) | Q(
                old_owner_accept_at__lt=timezone.now() - datetime.timedelta(
                    days=institute_conf.transfer_ownership_cleanup_offset_days)))
        transfer_ownerships_count = transfer_ownerships.count()
        transfer_ownerships.delete()
        logs_adder(
            f'Event: SCHEDULE, cleanup, transfer_ownerships, {transfer_ownerships_count}')


class APIKey(models.Model):
    id = models.UUIDField(primary_key=True, unique=True,
                          db_index=True, default=uuid.uuid4, editable=False)
    institute = models.ForeignKey(
        Institute, on_delete=models.CASCADE, db_index=True)
    api_key = models.CharField(max_length=460, db_index=True)
    roles = ArrayField(models.CharField(max_length=100),
                       default=list, 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(db_index=True, null=True, blank=True)

    def __str__(self):
        return self.institute.name

    @staticmethod
    def create_api_key(institute):
        return jwt.encode(
            {'iss': settings.JWT_ISSUER, 'aud': settings.JWT_AUDIENCE,
             'institute_id': str(institute.id), 'random_key': uuid.uuid4().hex},
            settings.SECRET_KEY, algorithm='HS256')

    @staticmethod
    def create_record(institute, roles, expire_at):
        api_key = APIKey.create_api_key(institute)
        APIKey.objects.create(
            institute=institute,
            api_key=api_key,
            roles=roles,
            expire_at=expire_at,
        )
        return api_key


class InstituteJWT(models.Model):
    id = models.BigAutoField(primary_key=True, unique=True, db_index=True)
    api_key = models.ForeignKey(
        APIKey, 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(api_key):
        return jwt.encode(
            {'exp': datetime.datetime.now() + datetime.timedelta(days=institute_conf.institute_jwt_expire_time_in_days),
             'iss': settings.JWT_ISSUER, 'aud': settings.JWT_AUDIENCE, 'api_key_id': str(api_key.id),
             'random_key': uuid.uuid4().hex}, settings.SECRET_KEY, algorithm='HS256')

    @staticmethod
    def create_record(api_key):
        token = InstituteJWT.create_jwt(api_key)
        InstituteJWT.objects.create(
            api_key=api_key,
            token=token,
            expire_at=timezone.now() +
                      datetime.timedelta(
                          days=institute_conf.institute_jwt_expire_time_in_days)
        )
        return token

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


class InstituteStudent(models.Model):
    id = models.UUIDField(primary_key=True, unique=True,
                          db_index=True, default=uuid.uuid4, editable=False)
    referer_identity = models.CharField(max_length=100, db_index=True)
    institute = models.ForeignKey(
        Institute, on_delete=models.PROTECT, db_index=True)
    first_name = models.CharField(
        max_length=46, db_index=True, null=True, blank=True)
    last_name = models.CharField(
        max_length=46, db_index=True, null=True, blank=True)
    email = models.CharField(
        max_length=254, db_index=True, null=True, blank=True)
    phone = models.CharField(
        max_length=20, db_index=True, null=True, blank=True)
    image = models.ImageField(
        upload_to='institute_student/image', storage=public_storage, null=True, blank=True)
    image_thumbnail = models.ImageField(upload_to='institute_student/image_thumbnail',
                                        storage=public_storage, null=True, blank=True)
    # JSONField uses jsonb by default
    info = models.JSONField(null=True, blank=True)
    wikiazma_account = models.ForeignKey(
        User, on_delete=models.SET_NULL, db_index=True, null=True, blank=False)
    created_at = models.DateTimeField(auto_now_add=True, db_index=True)
    modified_at = models.DateTimeField(auto_now=True, db_index=True)

    class Meta:
        unique_together = ['institute', 'referer_identity']

    @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 "-"

    def __str__(self):
        return self.full_name
