import random
import string
import xlrd

from django.conf import settings
from django.contrib.auth import get_user_model
from django.utils.translation import ugettext as _

from mtp.apps.common.notifications.tasks import send_async

from .constants import (
    INTERNAL_GROUP_TRAINEE,
    INTERNAL_GROUP_TRAINER,
    INTERNAL_GROUP_WHOSWHO,
)
from .models import (
    Group,
    GroupRole,
    GroupSet,
    MTPUser,
)


def generate_password():
    characters = string.ascii_letters + string.digits + string.punctuation
    password = ''.join(random.choice(characters) for x in range(8))
    return password


REQUIRED_PWD_CHARACTER_GROUPS = (
    string.ascii_uppercase,
    string.ascii_lowercase,
    string.digits,
    string.punctuation,
)


def validate_extra_secure_password(password, previous=None):
    """
    Make sure the password is valid according to the extra security policy.

    Return:
    - is_valid: boolean
    - msg: string, the error message, or None
    - code: string, the error code, or None
    """

    # Password must be at least 8 characters long.
    if len(password) < 8:
        msg = _(u"Your password must contain at least 8 characters.")
        code = 'invalid-length'
        return False, msg, code

    # Password must contain at least:
    # - an uppercase letter,
    # - a lowercase letter,
    # - a digit,
    # - a special character.

    for character_group in REQUIRED_PWD_CHARACTER_GROUPS:
        if not len(set(character_group).intersection(password)):
            msg = _(
                u"Your new password must contain at least one uppercase letter, "
                u"one lowercase letter, one digit and one special character."
            )
            code = 'missing-characters'
            return False, msg, code

    # Password must be different from the previous password.
    if password == previous:
        msg = _(u"Your password must be different from your previous one.")
        code = 'identical-previous'
        return False, msg, code

    # Password is valid.
    return True, None, None


def init_user(user, password, groupset, is_staff):
    if groupset:
        # We cannot use ``bulk_create`` because it does not call the ``save`` method and the
        # ``Syncable`` models need to call the ``save`` method for each update.
        for group in groupset.mgroups.all():
            GroupRole.objects.get_or_create(user=user, group=group)

    whoswho_group = Group.objects.get(name=INTERNAL_GROUP_WHOSWHO)
    GroupRole.objects.get_or_create(user=user, group=whoswho_group)

    if is_staff:
        trainer_group = Group.objects.get(name=INTERNAL_GROUP_TRAINER)
        GroupRole.objects.get_or_create(user=user, group=trainer_group)
        user.is_staff = True
    else:
        trainee_group = Group.objects.get(name=INTERNAL_GROUP_TRAINEE)
        GroupRole.objects.get_or_create(user=user, group=trainee_group)

    if not password or password == "":
        password = generate_password()

    user.set_password(password)
    user.save()

    return user, password


class XLSTraineeImporter:
    """Import trainees from an xls file

    Attributes
    ----------
    xls_sheet: ``Sheet`` instance
        The xls sheet containing the user to import

    """

    def __init__(self, file_name):
        """Get and define the sheet to extract the trainees from

        Parameters
        ----------
        file_name: str
            The absolute path to the xls file containing the trainees to import

        """

        trainee_reader = xlrd.open_workbook(filename=file_name)

        self.xls_sheet = trainee_reader.sheet_by_name(
            trainee_reader.sheet_names()[0]
        )

    def import_users(self, commit=False, send_mail=False):
        """Import the users from the xls file

        If the user don't exists, he will be created.
        If the user already exists, he will be updated.

        Parameters
        ----------
        commit: bool
            If True, the users will be persisted in the database
        send_mail: bool
            If True, a mail will be sent to each new user

        Returns
        -------
        list(MTPUser)
            A list of ``MTPUser`` instance

        Notes
        -----
        The first row is containing the column titles.

        """

        return [
            self.import_user(row, commit=commit, send_mail=send_mail)
            for row in range(1, self.xls_sheet.nrows)
        ]

    @staticmethod
    def format_password(raw):
        """We format the provided password to a correct password.

        Parameters
        ----------
        raw: any
            The raw password to format in a correct string.
            It can be a string, a number, ... or other.

        Returns
        -------
        str
            The password with the correct format.

        """

        # No need to format the password if it is already a string
        if isinstance(raw, str):
            return raw

        try:
            # We test if ``raw`` is a number
            __ = float(raw)
        except ValueError:  # ``raw`` is not a number
            password = raw
        else:  # ``raw`` is a number
            # If the number is an integer (ie ``123.0`` ) we remove the decimal part (ie ``.0``)
            password = int(raw) if (raw % 1) == 0 else raw

        return str(password)

    def import_user(self, row, commit, send_mail):
        """Import a user from the given row of the xls sheet

        Parameters
        ----------
        row: ``Row`` instance
            The row we intend to extract the user from
        commit: bool
            If True, the user will be persisted in database
        send_mail: bool
            If True, a mail is send if the imported user is a new one

        """

        row_values = self.xls_sheet.row_values(row)

        username = row_values[0].lower().strip()
        first_name = row_values[1].title()
        last_name = row_values[2].upper().strip()
        email = row_values[3].strip().lower()

        try:
            password = row_values[4]
        except IndexError:
            password = '123456'
        else:
            password = self.format_password(password)

        try:
            groupset = GroupSet.objects.get(name=row_values[5].strip())
        except (IndexError, GroupSet.DoesNotExist):
            groupset = None

        new_trainee = False

        try:
            user = get_user_model().objects.get(username=username)
            user.first_name = first_name
            user.last_name = last_name
            user.email = email
        except get_user_model().DoesNotExist:
            user = get_user_model()(
                username=username,
                first_name=first_name,
                last_name=last_name,
                email=email,
                created_from=MTPUser.ORIGINS.CMS_IMPORT,
            )
            new_trainee = True

        if commit:
            user.is_deleted = False
            user.save()
            init_user(user, password, groupset, False)
            if new_trainee and send_mail and settings.INSTANCE_IS_MASTER:
                send_async.delay([user], 'user_welcome', {
                    'password': password
                })

        return user
