import json
import random
import re
import string
from hashlib import md5

import pytz
from django.conf import settings
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.crypto import get_random_string
from django.utils.translation import ugettext_lazy as _


def nick_validator(value: str) -> None:
    """
    Validate nicknames for length, collisions when lower-cased, and allowed
    characters (ascii letters, digits, -, _). To be used in URL.
    """
    if not 2 <= len(value) <= 60:
        raise ValidationError('The nick must be between 2 and 60 characters long.')

    allowed = string.ascii_uppercase + string.ascii_lowercase + string.digits + '\-_'
    if not re.compile(rf'^[{allowed}]+$').search(value):
        raise ValidationError('The nick may only contain ascii letters, digits and -_.')


class UserManager(BaseUserManager):
    """
    The user manager class
    """
    def create_user(self, nick: str, password: str=None, **kwargs):
        user = self.model(nick=nick, **kwargs)
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, nick: str, password: str, **kwargs):
        user = self.create_user(nick=nick, password=password, **kwargs)
        user.is_staff = True
        user.is_superuser = True
        user.save(update_fields=['is_staff', 'is_superuser'])
        return user


def assign_code(obj, length=6):
    # This omits some character pairs completely because they are hard to read even on screens (1/I and O/0)
    # and includes only one of two characters for some pairs because they are sometimes hard to distinguish in
    # handwriting (2/Z, 4/A, 5/S, 6/G).
    while True:
        code = get_random_string(length=length, allowed_chars=User.CODE_CHARSET)

        if not User.objects.filter(code=code).exists():
            obj.code = code
            return code


class User(AbstractBaseUser):
    """
    The pretalx user model: We don't really need last names and fancy stuff, so
    we stick with a nick, and optionally a name and an email address.
    """
    EMAIL_FIELD = 'email'
    USERNAME_FIELD = 'nick'
    CODE_CHARSET = list('ABCDEFGHJKLMNPQRSTUVWXYZ3789')

    objects = UserManager()

    code = models.CharField(
        max_length=16,
        unique=True, null=True,
    )
    nick = models.CharField(
        max_length=60, unique=True, validators=[nick_validator],
        verbose_name=_('Nickname'),
        help_text=_('Please use only characters in the latin alphabet, plus numbers and _-.'),
    )
    name = models.CharField(
        max_length=120, null=True, blank=True, verbose_name=_('Name'),
        help_text=_('Please enter the name you wish to be displayed.'),
    )
    email = models.EmailField(
        verbose_name=_('E-Mail'),
        help_text=_('Your email address will be used for password resets and notification about your event/submissions.'),
    )
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)
    locale = models.CharField(max_length=32, default=settings.LANGUAGE_CODE,
                              choices=settings.LANGUAGES,
                              verbose_name=_('Preferred language'))
    timezone = models.CharField(
        choices=[(tz, tz) for tz in pytz.common_timezones],
        max_length=30,
        default='UTC',
    )
    avatar = models.ImageField(
        null=True, blank=True,
        verbose_name=_('Profile picture'),
        help_text=_('Optional. Will be displayed publically.'),
    )
    get_gravatar = models.BooleanField(
        default=False,
        verbose_name=_('Retrieve profile picture via gravatar'),
        help_text=_('If you have registered with an email address that has a gravatar account, we can retrieve your profile picture from there.'),
    )
    pw_reset_token = models.CharField(null=True, max_length=160)
    pw_reset_time = models.DateTimeField(null=True)

    def __str__(self) -> str:
        return self.get_display_name()

    def get_full_name(self) -> str:
        if self.name:
            return f"{self.name} ('{self.nick}')"
        return self.nick

    def get_display_name(self) -> str:
        return self.name if self.name else self.nick

    def get_short_name(self) -> str:
        return self.nick

    def save(self, *args, **kwargs):
        self.email = self.email.lower()
        if not self.code:
            assign_code(self)
        return super().save(args, kwargs)

    def log_action(self, action, data=None, orga=False):
        from pretalx.common.models import ActivityLog
        if data:
            data = json.dumps(data)

        ActivityLog.objects.create(
            person=self, content_object=self, action_type=action,
            data=data, is_orga_action=orga,
        )

    def logged_actions(self):
        from pretalx.common.models import ActivityLog

        return ActivityLog.objects.filter(
            content_type=ContentType.objects.get_for_model(type(self)),
            object_id=self.pk
        )

    def own_actions(self):
        from pretalx.common.models import ActivityLog
        return ActivityLog.objects.filter(person=self)

    def deactivate(self):
        from pretalx.person.models import EventPermission
        self.nick = f'deleted_user_{random.randint(0, 999)}'
        self.name = 'Deleted User'
        self.email = f'{self.nick}@localhost'
        self.is_active = False
        self.is_superuser = False
        self.locale = 'en'
        self.timezone = 'UTC'
        self.pw_reset_token = None
        self.pw_reset_time = None
        self.save()
        self.profiles.all().update(biography='')
        EventPermission.objects.filter(user=self).update(is_orga=False, invitation_email=None, invitation_token=None)

    @property
    def gravatar_parameter(self):
        return md5(self.email.strip().encode()).hexdigest()
