import datetime
import subprocess
from typing import Dict, List, Optional, Set

from gitlab.exceptions import GitlabGetError, GitlabListError

from api.canvas_classes import CanvasCourse, CanvasAssignment, CanvasUser
from api.composite_user import CompositeUser
from api.gitlab_classes import GitlabUser, GitlabProject, GitlabCommit
from common_functions import select_from_list, strip_html, semester_stamp
from course import Course

DEFAULT_HOMEWORK_REPOSITORY = 'csce361-homework'


def get_responses(assignment: CanvasAssignment, students: List[CanvasUser]) -> Dict[CanvasUser, str]:
    # TODO: Can this be made more general and be useful to grade_team_contribution.display_peer_reviews() ?
    student_responses: Dict[CanvasUser, Optional[str]] = {}
    # noinspection PyUnusedLocal
    student: CanvasUser
    for student in students:
        student_responses[student] = strip_html(assignment.get_submission_text(student))
    return student_responses


def extract_gitlab_usernames(student_responses: Dict[CanvasUser, str]) -> Dict[CanvasUser, str]:
    student_gitlab_usernames: Dict[CanvasUser, str] = {}
    # noinspection PyUnusedLocal
    student: CanvasUser
    for student in student_responses:
        response: str = student_responses[student]
        if response is None:
            response = ''
        candidate_username: List[str] = response.split()
        if len(candidate_username) == 1:
            username: str = candidate_username[0]
            if username[0] == '@':
                student_gitlab_usernames[student] = username[1:]
            else:
                student_gitlab_usernames[student] = username
        elif len(candidate_username) > 1:
            print(f'Unexpected response from {student.get_name()}: {response}')
            potential_usernames = list(filter(lambda s: s[0] == '@', candidate_username))
            if len(potential_usernames) == 1:
                username: str = potential_usernames[0]
                print(f'Assigning {username} as username.')
                student_gitlab_usernames[student] = username[1:]
            else:
                username: str = input('Enter gitlab username: ')
                student_gitlab_usernames[student] = username
        else:
            print(f'{student.get_name()} has no response; assigning blank gitlab username.')
            student_gitlab_usernames[student] = ''
        print(f'\tCanvas username: {student.get_username()}\tGitlab username: @{student_gitlab_usernames[student]}')
    return student_gitlab_usernames


def retrieve_gitlab_user(gitlab_username: str, student_name: str) -> Optional[GitlabUser]:
    try:
        if gitlab_username != '':
            return GitlabUser(gitlab_username)
        else:
            return None
    except IndexError:
        print(f'\t{student_name} has an invalid gitlab username: {gitlab_username}.')
        new_username: str = input('\tEnter new gitlab username (enter blank to skip): ')
        return retrieve_gitlab_user(new_username, student_name)


# noinspection PyShadowingNames
def create_composite_users(student_usernames: Dict[CanvasUser, str]) -> (Set[CompositeUser], Set[CanvasUser]):
    composite_users: Set[CompositeUser] = set()
    skipped_users: Set[CanvasUser] = set()
    # noinspection PyUnusedLocal
    canvas_student: CanvasUser
    for canvas_student in student_usernames:
        gitlab_username: str = student_usernames[canvas_student]
        gitlab_student: GitlabUser = retrieve_gitlab_user(gitlab_username, canvas_student.get_name())
        if gitlab_student is not None:
            composite_user: CompositeUser = CompositeUser.initialize_composite_user(canvas_student, gitlab_student)
            composite_users.add(composite_user)
        else:
            print(f'{canvas_student.get_name()} has a blank gitlab username; skipping.')
            skipped_users.add(canvas_student)
    return composite_users, skipped_users


def retrieve_homework_repos(students: Set[CompositeUser], repo_name: str) \
        -> Dict[CompositeUser, Optional[GitlabProject]]:
    user_repos: Dict[CompositeUser, GitlabProject] = {}
    for student in students:
        path = student.get_gitlab_user().get_username() + '/' + repo_name
        try:
            print(f'\tRetrieving {path}.git at {datetime.datetime.now()}.')
            repo = GitlabProject(path)
        except GitlabGetError:
            print(f'*** WARNING *** Could not retrieve {path}.git!')
            repo = None
        user_repos[student] = repo
    return user_repos


def add_gitlab_email(user_repos: Dict[CompositeUser, Optional[GitlabProject]]) -> None:
    # noinspection PyUnusedLocal
    student: CompositeUser
    for student in user_repos:
        project = user_repos[student]
        if project is not None:
            try:
                commits: List[GitlabCommit] = project.get_commits()
            except GitlabListError:
                print(f'*** NOTE *** Could not access commits for {project}. '
                      f'This probably indicates Guest permissions.')
                commits = []
            if len(commits) > 0:
                commit = commits[-1]  # should be the initial commit; regardless, all commits should be by the student
                email = commit.get_author()['email']
                student.set_gitlab_email(email)


def create_cloning_script(user_repos: Dict[CompositeUser, Optional[GitlabProject]],
                          no_username: Set[CanvasUser], filename: str) -> None:
    file = open(filename, 'x')
    file.write('#!/bin/bash\n\n')
    file.write('# Auto-generated clone script.\n')
    no_repo: Set[CompositeUser] = set()
    # noinspection PyUnusedLocal
    composite_student: CompositeUser
    for composite_student in user_repos:
        repo = user_repos[composite_student]
        if repo is not None:
            repo_url = repo.get_cloning_url()
            student_name = composite_student.get_canvas_user().get_name().replace(' ', '_')
            file.write(f'git clone {repo_url} {student_name}')
            if composite_student.gitlab_email is None:  # breaking encapsulation?
                file.write('  # may have inadequate permissions')
            file.write('\n')
        else:
            no_repo.add(composite_student)
    if len(no_repo) > 0:
        file.write('\n# Students without homework repositories '
                   '(check for misspelled repository names or repositories created but not shared):\n')
    for composite_student in no_repo:
        student_name = composite_student.get_canvas_user().get_name().replace(' ', '_')
        file.write(f'# git clone {composite_student.get_gitlab_user().get_username()}/... {student_name}\n')
    if len(no_username) > 0:
        file.write('\n# Students without valid gitlab usernames (check for repositories shared but username not '
                   'submitted):\n')
    # noinspection PyUnusedLocal
    canvas_student: CanvasUser
    for canvas_student in no_username:
        student_name = canvas_student.get_name().replace(' ', '_')
        file.write(f'# git clone ... {student_name}\n')
    file.close()
    subprocess.call(['chmod', '+x', filename])
    print(f'\tCreated {filename}. Distribute to TAs so they can clone the homework repositories.')


if __name__ == '__main__':
    course = CanvasCourse(Course.canvas_course_id)
    print('First, select the "setup" assignment to extract student information from.\n')
    assignment_groups = course.get_assignment_groups()
    assignment_group = select_from_list(assignment_groups, 'assignment group')
    print()
    assignments = assignment_group.get_assignments()
    setup_assignment = select_from_list(assignments, 'assignment')
    print(f'\nWe will now extract student information from {setup_assignment}.')
    responses = get_responses(setup_assignment, course.get_students())
    gitlab_usernames = extract_gitlab_usernames(responses)
    print('\nWe will now check the validity of the gitlab usernames.')
    composite_users, skipped_users = create_composite_users(gitlab_usernames)
    if len(skipped_users) == 0:
        print(f'\nSuccessfully retrieved {len(composite_users)} students.')
    else:
        print(
            f'\n*** WARNING *** Skipped {len(skipped_users)} '
            f'out of {len(skipped_users)+len(composite_users)} students!')
    print('We will now retrieve the students\' homework repositories.')
    choice = input(f'Enter homework repository name [{DEFAULT_HOMEWORK_REPOSITORY}]: ')
    if choice == '':
        choice = DEFAULT_HOMEWORK_REPOSITORY
    homework_repos = retrieve_homework_repos(composite_users, choice)
    add_gitlab_email(homework_repos)
    print('\nCreating cloning script for homework repositories.')
    create_cloning_script(homework_repos, skipped_users, f'{semester_stamp()}-homework.sh')
    CompositeUser.write_student_csv(set(homework_repos.keys()), f'{semester_stamp()}-students.csv')
    print('\tCreated student csv file.')