import configparser
import secrets
import string

from core.models import UserAccount as User
from core.models import (ExamType, Feedback, StudentInfo, Submission,
                         SubmissionType)

STUDENTS = 'students'
TUTORS = 'tutors'
REVIEWERS = 'reviewers'

PASSWORDS = '.importer_passwords'


def get_random_password(length=32):
    """ Returns a cryptographically random string of specified length """
    return ''.join(secrets.choice(string.ascii_lowercase)
                   for _ in range(length))


def store_password(username, groupname, password):
    storage = configparser.ConfigParser()
    storage.read(PASSWORDS)

    if groupname not in storage:
        storage[groupname] = {}

    storage[groupname][username] = password

    with open(PASSWORDS, 'w') as passwd_file:
        storage.write(passwd_file)


class GradyUserFactory:

    def __init__(self,
                 make_password=get_random_password,
                 password_storge=store_password,
                 *args, **kwargs):
        self.make_password = make_password
        self.password_storge = password_storge

    def _get_random_name(self, prefix='', suffix='', k=4):
        return ''.join((prefix, self.make_password(k), suffix))

    def _get_group_for_user_role(self, role):
        """ Returns the groupname for a role """
        return {
            'Student': 'students',
            'Tutor': 'tutors',
            'Reviewer': 'reviewers'
        }[role]

    def _make_base_user(self, username, role, password=None,
                        store_pw=False, **kwargs):
        """ This is a specific wrapper for the django update_or_create method of
        objects.
            * If now username is passed, a generic one will be generated
            * A new user is created and password and role are set accordingly
            * If the user was there before password IS changed
            * A user must only have one role.

        Returns:
            (User object, str): The user object that was added to the role and
            the password of that user if it was created.
        """
        if not username:
            username = self._get_random_name(prefix=role.lower() + '_')

        username = username.strip()

        user, created = User.objects.update_or_create(
            username=username,
            role=role,
            defaults=kwargs)

        if created:
            password = self.make_password() if password is None else password
            user.set_password(password)
            user.save()

        if created and store_pw:
            self.password_storge(
                username,
                self._get_group_for_user_role(role),
                password)

        return user

    def make_student(self, username=None, matrikel_no=None,
                     exam=None, **kwargs):
        """ Creates a student. Defaults can be passed via kwargs like in
        relation managers objects.update method. """
        user = self._make_base_user(username, 'Student', **kwargs)
        studentInfo = StudentInfo.objects.get_or_create(user=user)[0]
        if matrikel_no:
            studentInfo.matrikel_no = matrikel_no
        if exam:
            studentInfo.exam = exam
        studentInfo.save()
        return user

    def make_tutor(self, username=None, **kwargs):
        """ Creates or updates a tutor if needed with defaults """
        return self._make_base_user(username, 'Tutor', **kwargs)

    def make_reviewer(self, username=None, **kwargs):
        """ Creates or updates a reviewer if needed with defaults """
        return self._make_base_user(username, 'Reviewer', **kwargs)


def make_exams(exams=[], **kwargs):
    return [ExamType.objects.get_or_create(
        module_reference=exam['module_reference'],
        defaults=exam)[0] for exam in exams]


def make_submission_types(submission_types=[], **kwargs):
    return [SubmissionType.objects.get_or_create(
        name=submission_type['name'], defaults=submission_type)[0]
        for submission_type in submission_types]


def make_students(students=[], **kwargs):

    return [GradyUserFactory().make_student(
        username=student['username'],
        exam=ExamType.objects.get(
            module_reference=student['exam']) if 'exam' in student else None
    ) for student in students]


def make_tutors(tutors=[], **kwargs):
    return [GradyUserFactory().make_tutor(**tutor)
            for tutor in tutors]


def make_reviewers(reviewers=[], **kwargs):
    return [GradyUserFactory().make_reviewer(**reviewer)
            for reviewer in reviewers]


def make_feedback(feedback, submission_object):
    feedback['of_tutor'] = User.objects.get(
        username=feedback['of_tutor'])
    return Feedback.objects.update_or_create(
        of_submission=submission_object,
        defaults=feedback)[0]


def make_submissions(submissions=[], **kwargs):
    submission_objects = []
    for submission in submissions:
        submission_type, _ = SubmissionType.objects.get_or_create(
            name=submission.get('type', 'Auto generated type'))
        student, _ = StudentInfo.objects.get_or_create(user=User.objects.get(
            username=submission.get('user', 'default_user')
        ))
        submission_object, _ = Submission.objects.get_or_create(
            type=submission_type, student=student, defaults={
                'seen_by_student': submission.get('seen_by_student', False),
                'text': submission.get('text', ''),
            })
        if 'feedback' in submission:
            make_feedback(submission['feedback'], submission_object)
        submission_objects.append(submission_object)
    return submission_objects


def make_test_data(data_dict):
    return {
        'exams': make_exams(**data_dict),
        'submission_types': make_submission_types(**data_dict),
        'students': make_students(**data_dict),
        'tutors': make_tutors(**data_dict),
        'reviewers': make_reviewers(**data_dict),
        'submissions': make_submissions(**data_dict)
    }


def init_test_instance():
    return make_test_data(
        data_dict={
            'exams': [{
                'module_reference': 'Test Exam 01',
                'total_score': 100,
                'pass_score': 60,
            }],
            'submission_types': [
                {
                    'name': '01. Sort this or that',
                    'full_score': 35,
                    'description': 'Very complicated',
                    'solution': 'Trivial!'
                },
                {
                    'name': '02. Merge this or that or maybe even this',
                    'full_score': 35,
                    'description': 'Very complicated',
                    'solution': 'Trivial!'
                },
                {
                    'name': '03. This one exists for the sole purpose to test',
                    'full_score': 30,
                    'description': 'Very complicated',
                    'solution': 'Trivial!'
                }
            ],
            'students': [{
                'username': 'student01',
                'exam': 'Test Exam 01',
            },
                {
                'username': 'student02',
                'exam': 'Test Exam 01',
            }],
            'tutors': [{
                'username': 'tutor01'
            }],
            'reviewers': [{
                'username': 'reviewer01',
                'password': 'p'
            }],
            'submissions': [
                {
                    'text': 'function blabl\n'
                            '   on multi lines\n'
                            '       for blabla in bla:\n'
                            '   arrrgh\n'
                            '       asasxasx\n'
                            '           lorem ipsum und so\n',
                    'type': '01. Sort this or that',
                    'user': 'student01',
                    'feedback': {
                        'text': 'Not good!',
                        'score': 5,
                        'of_tutor': 'tutor01',
                        'is_final': True
                    }
                },
                {
                    'text': 'function blabl\n'
                            '   on multi lines\n'
                            '       for blabla in bla:\n'
                            '   arrrgh\n'
                            '       asasxasx\n'
                            '           lorem ipsum und so\n',
                    'type': '02. Merge this or that or maybe even this',
                    'user': 'student01'
                },
                {
                    'text': 'function blabl\n'
                            '   on multi lines\n'
                            '       for blabla in bla:\n'
                            '   arrrgh\n'
                            '       asasxasx\n'
                            '           lorem ipsum und so\n',
                    'type': '03. This one exists for the sole purpose to test',
                    'user': 'student01'
                },
                {
                    'text': 'function blabl\n'
                            '   on multi lines\n'
                            '       for blabla in bla:\n'
                            '   arrrgh\n'
                            '       asasxasx\n'
                            '           lorem ipsum und so\n',
                    'type': '03. This one exists for the sole purpose to test',
                    'user': 'student02'
                },
            ]}
    )