import subprocess
from platform import system as system_name
import re
import os
from collections import defaultdict
import tempfile
import pathlib
import stat
import shutil
from datetime import datetime, timedelta

import click
import github
from rich import print as rprint

from pyneng_cli_course.exceptions import PynengError
from pyneng_cli_course import (
    ANSWERS_URL,
    TASKS_URL,
    TASK_DIRS,
    DB_TASK_DIRS,
    STUDENT_REPO_TEMPLATE,
)


def red(msg):
    return click.style(msg, fg="red")


def green(msg):
    return click.style(msg, fg="green")


def remove_readonly(func, path, _):
    """
    Вспомогательная функция для Windows, которая позволяет удалять
    read only файлы из каталога .git
    """
    os.chmod(path, stat.S_IWRITE)
    func(path)


def call_command(command, verbose=True, return_stdout=False, return_stderr=False):
    """
    Функция вызывает указанную command через subprocess
    и выводит stdout и stderr, если флаг verbose=True.
    """
    result = subprocess.run(
        command,
        shell=True,
        encoding="utf-8",
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )
    std = result.stdout
    stderr = result.stderr
    if return_stdout:
        return std
    if return_stderr:
        return result.returncode, stderr
    if verbose:
        print("#" * 20, command)
        if std:
            print(std)
        if stderr:
            print(stderr)
    return result.returncode


def working_dir_clean():
    git_status = call_command("git status --porcelain", return_stdout=True)
    if git_status:
        return False
    else:
        return True


def show_git_diff_short():
    git_diff = call_command("git diff --stat")


def git_push(branch):
    """
    Функция вызывает git push для Windows
    """
    command = f"git push origin {branch}"
    print("#" * 20, command)
    result = subprocess.run(command, shell=True)


def save_changes_to_github(
    message="Все изменения сохранены", git_add_all=True, branch="main"
):
    status = call_command("git status -s", return_stdout=True)
    if not status:
        return
    if git_add_all:
        call_command("git add .")
    call_command(f'git commit -m "{message}"')
    windows = True if system_name().lower() == "windows" else False

    if windows:
        git_push(branch)
    else:
        call_command(f"git push origin {branch}")


def get_repo(search_pattern=STUDENT_REPO_TEMPLATE):
    git_remote = call_command("git remote -v", return_stdout=True)
    repo_match = re.search(search_pattern, git_remote)
    if repo_match:
        repo = repo_match.group()
        return repo
    else:
        raise PynengError(
            red(
                f"Не найден репозиторий {STUDENT_REPO_TEMPLATE}. "
                f"pyneng надо вызывать в репозитории подготовленном для курса."
            )
        )


def test_run_for_github_token():
    """
    Функция добавляет тестовое сообщение к последнему за 2 недели коммиту
    """
    message = "Проверка работы токена прошла успешно"
    repo = get_repo()
    last = post_comment_to_last_commit(message, repo)
    commit_number = re.search(r'"(\w+)"', str(last)).group(1)
    print(
        green(
            f"Комментарий можно посмотреть по ссылке "
            f"https://github.com/pyneng/{repo}/commit/{commit_number}"
        )
    )


def post_comment_to_last_commit(msg, repo, delta_days=60, ignore_ssl_cert=False):
    """
    Написать комментарий о сдаче заданий в последнем коммите.
    Комментарий пишется через Github API.

    Для работы функции должен быть настроен git.
    Функция пытается определить имя пользователя git из вывода git config --list,
    Если это не получается, запрашивает имя пользователя.

    Пароль берется из переменной окружения GITHUB_PASS или запрашивается.
    """
    token = os.environ.get("GITHUB_TOKEN")
    since = datetime.now() - timedelta(days=delta_days)
    repo_name = f"pyneng/{repo}"
    verify_ssl_cert = False if ignore_ssl_cert else True
    try:
        g = github.Github(token, verify=verify_ssl_cert)
        repo_obj = g.get_repo(repo_name)
    except github.GithubException:
        raise PynengError(
            red("Аутентификация по токену не прошла. Задание не сдано на проверку")
        )
    else:
        commits = repo_obj.get_commits(since=since)

        try:
            last = commits[0]
        except IndexError:
            print(f"За указанный период времени {delta_days} дней не найдено коммитов")
        else:
            last.create_comment(msg)
            return last


def send_tasks_to_check(
    passed_tasks, git_add_all=False, ignore_ssl_cert=False, branch="main"
):
    """
    Функция отбирает все задания, которые прошли
    тесты при вызове pyneng, делает git add для файлов заданий,
    git commit с сообщением какие задания сделаны
    и git push для добавления изменений на Github.
    После этого к этому коммиту добавляется сообщение о том,
    что задания сдаются на проверку с помощью функции post_comment_to_last_commit.
    """
    ok_tasks = [
        re.sub(r".*(task_\d+_\w+.py)", r"\1", filename) for filename in passed_tasks
    ]
    tasks_num_only = sorted(
        [task.replace("task_", "").replace(".py", "") for task in ok_tasks]
    )
    message = f"Сделаны задания {' '.join(tasks_num_only)}"

    for task in ok_tasks:
        call_command(f"git add {task}")
        # добавление шаблонов для заданий jinja, textfsm
        if "20" in task or "21" in task:
            call_command("git add templates")
        elif "25" in task:
            call_command("git add .")
    save_changes_to_github(message, git_add_all=git_add_all, branch=branch)

    repo = get_repo()
    last = post_comment_to_last_commit(message, repo, ignore_ssl_cert=ignore_ssl_cert)
    commit_number = re.search(r'"(\w+)"', str(last)).group(1)
    print(
        green(
            f"Задание успешно сдано на проверку. Комментарий о сдаче задания "
            f"можно посмотреть по ссылке https://github.com/pyneng/{repo}/commit/{commit_number}"
        )
    )
    rprint(
        "Все задания раздела можно сдать командой:\n"
        "[green on black]pyneng -c[/]\n\n"
        "Не забудьте посмотреть варианты решения и комментарии в проверке.\n"
        "Как посмотреть варианты решения:\n"
        "[white on black]pyneng -a[/]"
    )


def current_chapter_id():
    """
    Функция возвращает номер текущего раздела, где вызывается pyneng.
    """
    current_chapter_name = current_dir_name()
    if current_chapter_name in DB_TASK_DIRS:
        current_chapter_name = TASK_DIRS[-1]
    current_chapter = int(current_chapter_name.split("_")[0])
    return current_chapter


def current_dir_name():
    pth = str(pathlib.Path().absolute())
    current_chapter_name = os.path.split(pth)[-1]
    return current_chapter_name


def parse_json_report(report):
    """
    Отбирает нужные части из отчета запуска pytest в формате JSON.
    Возвращает список тестов, которые прошли.
    """
    if report and report["summary"]["total"] != 0:
        all_tests = defaultdict(list)
        summary = report["summary"]

        test_names = [test["nodeid"] for test in report["collectors"][0]["result"]]
        for test in report["tests"]:
            name = test["nodeid"].split("::")[0]
            all_tests[name].append(test["outcome"] == "passed")
        all_passed_tasks = [name for name, outcome in all_tests.items() if all(outcome)]
        return all_passed_tasks
    else:
        return []


def git_clone_repo(repo_url, dst_dir):
    returncode, stderr = call_command(
        f"git clone {repo_url} {dst_dir}",
        verbose=False,
        return_stderr=True,
    )
    if returncode != 0:
        if "could not resolve host" in stderr.lower():
            raise PynengError(
                red(
                    "Не получилось клонировать репозиторий. Возможно нет доступа в интернет?"
                )
            )
        else:
            raise PynengError(red(f"Не получилось скопировать файлы. {stderr}"))


def copy_answers(passed_tasks):
    """
    Функция клонирует репозиторий с ответами и копирует ответы для заданий,
    которые прошли тесты.
    """
    pth = str(pathlib.Path().absolute())
    current_chapter_name = os.path.split(pth)[-1]
    current_chapter_number = int(current_chapter_name.split("_")[0])

    homedir = pathlib.Path.home()
    os.chdir(homedir)
    if os.path.exists("pyneng-answers"):
        shutil.rmtree("pyneng-answers", onerror=remove_readonly)
    git_clone_repo(ANSWERS_URL, "pyneng-answers")
    os.chdir(os.path.join("pyneng-answers", "answers", current_chapter_name))
    copy_answer_files(passed_tasks, pth)
    print(
        green(
            "\nОтветы на задания, которые прошли тесты "
            "скопированы в файлы answer_task_x.py\n"
        )
    )
    os.chdir(homedir)
    shutil.rmtree("pyneng-answers", onerror=remove_readonly)
    os.chdir(pth)


def copy_answer_files(passed_tasks, pth):
    """
    Функция копирует ответы для указанных заданий.
    """
    for test_file in passed_tasks:
        task_name = test_file.replace("test_", "")
        task_name = re.search(r"task_\w+\.py", task_name).group()
        answer_name = test_file.replace("test_", "answer_")
        answer_name = re.search(r"answer_task_\w+\.py", answer_name).group()
        pth_answer = os.path.join(pth, answer_name)
        if not os.path.exists(pth_answer):
            shutil.copy2(task_name, pth_answer)


def clone_or_pull_task_repo():
    course_tasks_repo_dir = ".pyneng-course-tasks"
    source_pth = str(pathlib.Path().absolute())
    homedir = pathlib.Path.home()
    os.chdir(homedir)
    if os.path.exists(course_tasks_repo_dir):
        os.chdir(course_tasks_repo_dir)
        call_command("git pull")
        os.chdir(homedir)
    else:
        git_clone_repo(TASKS_URL, course_tasks_repo_dir)
    os.chdir(source_pth)


def copy_tasks_tests_from_repo(tasks, tests):
    """
    Функция клонирует репозиторий с последней версией заданий и копирует указанные
    задания в текущий каталог.
    """
    source_pth = str(pathlib.Path().absolute())
    current_chapter_name = os.path.split(source_pth)[-1]
    current_chapter_number = int(current_chapter_name.split("_")[0])

    clone_or_pull_task_repo()

    course_tasks_repo_dir = ".pyneng-course-tasks"
    homedir = pathlib.Path.home()
    os.chdir(
        os.path.join(homedir, course_tasks_repo_dir, "exercises", current_chapter_name)
    )
    copy_task_test_files(source_pth, tasks, tests)
    print(green("\nОбновленные задания и тесты скопированы"))
    os.chdir(source_pth)


def copy_task_test_files(source_pth, tasks=None, tests=None):
    """
    Функция копирует файлы заданий и тестов.
    """
    file_list = []
    if tasks:
        file_list += tasks
    if tests:
        file_list += tests
    for file in file_list:
        shutil.copy2(file, os.path.join(source_pth, file))


def save_working_dir(branch="main"):
    if not working_dir_clean():
        print(
            red(
                "Обновление тестов и заданий перезапишет содержимое несохраненных файлов!".upper()
            )
        )
        user_input = input(
            red(
                "В текущем каталоге есть несохраненные изменения! "
                "Хотите их сохранить? [y/n]: "
            )
        )
        if user_input.strip().lower() not in ("n", "no"):
            save_changes_to_github(
                "Сохранение изменений перед обновлением заданий", branch=branch
            )
            print(
                green(
                    "Все изменения в текущем каталоге сохранены. Начинаем обновление..."
                )
            )


def working_dir_changed_diff(branch="main"):
    print(red("Были обновлены такие файлы:"))
    show_git_diff_short()
    print(
        "\nЭто короткий diff, если вы хотите посмотреть все отличия подробно, "
        "нажмите n и дайте команду git diff.\n"
        "Также при желании можно отменить внесенные изменения git checkout -- file "
        "(или git restore file)."
    )

    user_input = input(red("\nСохранить изменения и добавить на github? [y/n]: "))
    if user_input.strip().lower() not in ("n", "no"):
        save_changes_to_github("Обновление заданий", branch=branch)


def update_tasks_and_tests(tasks_list, tests_list, branch="main"):
    save_working_dir(branch=branch)
    copy_tasks_tests_from_repo(tasks_list, tests_list)
    if working_dir_clean():
        print(green("Задания и тесты уже последней версии"))
        return False
    else:
        working_dir_changed_diff(branch=branch)
        return True


def update_chapters_tasks_and_tests(update_chapters, branch="main"):
    save_working_dir(branch=branch)
    copy_chapters_from_repo(update_chapters)
    if working_dir_clean():
        print(green("Все разделы уже последней версии"))
        return False
    else:
        working_dir_changed_diff(branch=branch)
        return True


def copy_chapters_from_repo(chapters_list):
    """
    Функция клонирует репозиторий с последней версией заданий и копирует указанные
    задания в текущий каталог.
    """
    source_pth = str(pathlib.Path().absolute())
    clone_or_pull_task_repo()

    course_tasks_repo_dir = ".pyneng-course-tasks"
    homedir = pathlib.Path.home()
    os.chdir(os.path.join(homedir, course_tasks_repo_dir, "exercises"))
    copy_chapters(source_pth, chapters_list)
    print(green("\nОбновленные разделы скопированы"))
    os.chdir(source_pth)


def copy_chapters(source_pth, chapters_list):
    """
    Функция копирует разделы
    """
    for chapter in chapters_list:
        to_path = os.path.join(source_pth, chapter)
        if os.path.exists(to_path):
            shutil.rmtree(to_path)
        shutil.copytree(chapter, to_path)
