import json
import os
import re
from urllib.parse import urljoin, urlparse

import requests
from tqdm import tqdm

from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.core.management import BaseCommand
from django.core.validators import URLValidator
from django.db import transaction
from django.db.models.signals import post_save

from lms.courses.models import Course, CourseFile, CourseStudent
from lms.enrollments.models import EnrolledUser, Enrollment
from lms.mailing.receivers import staff_profile_in_mailing_post_save_handler
from lms.resources.models import TextResource, VideoResource
from lms.scorm.models import Scorm, ScormFile, ScormResourceStudentAttempt, ScormStudentAttempt
from lms.staff.loaders import modified_user_post_save_handler
from lms.staff.models import StaffProfile
from lms.staff.receivers import staff_profile_post_save_handler, user_post_save_handler

User = get_user_model()
VIDEO_RE = (
    r'([\w\W]*){iframe:(.*)}([\w\W]*)',  # видео-ресурс вида "текст {iframe:link} текст"
    r'([\w\W]*){.*\|(.*)\|.*}([\w\W]*)',  # видео-ресурс вида "текст {vh-player-js|<vh-id>|...} текст"
    r'([\w\W]*){yaplayer\|(.*)}([\w\W]*)',  # видео-русерс вида "текст {yaplayer|link} текст"
)


class Command(BaseCommand):
    help = "Импортировать данные по курсу, модулям и прогрессу студентов"

    def __init__(self):
        super().__init__()
        self.template = None

        self.course_data = None
        self.lessons_data = None
        self.users_data = None
        self.resources = None
        self.results = None

        self.course = None
        self.enrollment = None
        self.modules_map = {}
        self.students_map = {}
        self.scorms_dir = '.scorms/'
        self.is_valid = URLValidator()

    def add_arguments(self, parser):
        parser.add_argument(
            '--input', type=str,
            help='Имя файла, из которого достаем json'
        )
        parser.add_argument(
            '--template', type=str,
            help='Тип курса (video/text/scorm)'
        )
        parser.add_argument(
            '--no_students', type=bool, default=False,
            help='Не подгружаем студентов'
        )
        parser.add_argument(
            '--no_results', type=bool, default=False,
            help='Не подгружаем прогресс'
        )
        parser.add_argument(
            '--show_scorms', type=bool, default=False,
            help='Показать ссылки на скормы'
        )

    def parse_video_link(self, markup):
        content = []
        for video_regex in VIDEO_RE:
            try:
                content = list(re.search(video_regex, markup).groups())
                break
            except AttributeError:
                self.stderr.write(f"Контент не соответствует формату видео-ресурса:\n{markup}")
                continue

        if not content:
            return

        url = content.pop(1)
        try:
            self.is_valid(url)
        except ValidationError:
            url = 'https://frontend.vh.yandex-team.ru/player/' + url

        video_link = urljoin(url, urlparse(url).path)  # убираем query параметры из урла
        description = '\n'.join(content)

        return description, video_link

    def create_students(self):
        user_moe_id_map = {}
        all_users = {}

        for user_data in self.users_data:
            username = user_data['username']
            user_moe_id_map[username] = user_data['id']
            all_users[username] = user_data['yauid']

        existed_users = set(User.objects.filter(username__in=all_users.keys()).values_list('username', flat=True))
        new_users = set(all_users.keys()) - existed_users

        for new_user in tqdm(new_users):
            User.objects.create(
                username=new_user,
                yauid=all_users[new_user],
            )

        self.stdout.write(f"Создано {len(new_users)} пользователей")

        not_enrolled_users = User.objects.filter(
            username__in=all_users.keys()
        ).exclude(enrolled_to__course=self.course)

        for user in tqdm(not_enrolled_users):
            EnrolledUser.objects.create(
                course=self.course,
                enrollment=self.enrollment,
                user=user,
                status=EnrolledUser.StatusChoices.ENROLLED,
            )

        self.stdout.write(f"Создано {not_enrolled_users.count()} заявок")

        course_students = CourseStudent.objects.filter(
            course=self.course,
            user__username__in=all_users.keys()
        ).select_related('user')

        for course_student in course_students:
            self.students_map[user_moe_id_map[course_student.user.username]] = course_student

        self.stdout.write(f"Создано {len(self.users_data)} студентов")

    def create_course(self):
        owner = self.course_data['owner']
        author, created = User.objects.get_or_create(
            username=owner['username'],
            defaults={'yauid': owner['yauid']},
        )

        desc = self.course_data['description']
        content_attr = 'summary' if len(desc) < 500 else 'description'
        self.course, created = Course.objects.get_or_create(
            slug=f"moe-{self.course_data['id']}",
            defaults={
                'name': self.course_data['name'],
                content_attr: desc,
                'author': author,
                'created': self.course_data['created'],
                'modified': self.course_data['modified'],
                'structure': Course.StructureChoices.MULTI,
                'retries_allowed': True,
            }
        )

        if created:
            self.stdout.write(f"Создан курс {self.course_data['name']}: {self.course.admin_url}")

        self.enrollment, created = Enrollment.objects.get_or_create(
            course=self.course,
            enroll_type=Enrollment.TYPE_INSTANT,
            defaults={'name': 'Автоматическое зачисление'},
        )

        # if self.template == 'scorm':
        #     self.preload_scorms()

        self.create_modules()

        self.stdout.write(f"Создано {len(self.modules_map)} модулей")

    def create_modules(self):
        create_module = getattr(self, f"create_module_{self.template}", None)
        if not create_module:
            self.stderr.write(f"Шаблон {self.template} не найден")
            return

        for lesson_data in tqdm(self.lessons_data):
            if self.template == 'scorm':
                create_module(lesson_data)
            else:
                for i, problem in enumerate(lesson_data['problems']):
                    name = lesson_data['name']
                    if len(lesson_data['problems']) > 1:
                        name += f' часть {i + 1}'
                    create_module(problem, name=name)

    def create_module_video(self, problem, name):
        if not problem['content']:
            return

        content = self.parse_video_link(problem['content'])
        if not content:
            self.create_module_text(problem, name)
            return

        description, video_link = content
        moe_id = problem['id']
        video_resource, created = VideoResource.objects.get_or_create(
            moe_id=moe_id,
            defaults={
                'course': self.course,
                'name': name,
                'description': description,
                'url': video_link,
            }
        )
        self.modules_map[moe_id] = video_resource

        if created:
            self.stdout.write(f"Создан модуль-видео {name}")

    def create_module_text(self, problem, name):
        if not problem['content']:
            return

        moe_id = problem['id']
        text_resource, created = TextResource.objects.get_or_create(
            moe_id=moe_id,
            defaults={
                'course': self.course,
                'name': name,
                'content': problem['content'],
            }
        )
        self.modules_map[moe_id] = text_resource

        if created:
            self.stdout.write(f"Создан текстовый модуль {name}")

    def get_scorm_url(self, lesson_data):
        try:
            resource_id = lesson_data['problems'][0]['content']['layout'][0]['content']['options']['sco']['id']
            return self.resources[str(resource_id)]['file']
        except (KeyError, IndexError, TypeError) as exc:
            self.stderr.write(f"Отсутствует ресурс в ресурсах курса или задачи {lesson_data['name']}\n{exc}")

    def download_scorm(self, scorm_url, chunk_size=128):
        save_path = self.scorms_dir + os.path.basename(scorm_url)
        if os.path.exists(save_path):
            return

        r = requests.get(scorm_url, stream=True)
        with open(save_path, 'wb') as file:
            for chunk in r.iter_content(chunk_size=chunk_size):
                file.write(chunk)

        self.stdout.write(f"Скорм {save_path} загружен локально")

    # def preload_scorms(self):
    #     if not os.path.exists(self.scorms_dir):
    #         os.makedirs(self.scorms_dir)
    #
    #     for lesson_data in tqdm(self.lessons_data):
    #         scorm_url = self.get_scorm_url(lesson_data)
    #         self.download_scorm(scorm_url)

    def create_scorm_file(self, lesson_data, scorm):
        scorm_url = self.get_scorm_url(lesson_data)
        if not scorm_url:
            return

        scorm_name = os.path.basename(scorm_url)
        s3_file_path = f'{settings.S3UPLOAD_BUCKET_ENDPOINT}/uploads/moe_scorms/{scorm_name}'

        course_file, created = CourseFile.objects.get_or_create(
            course=self.course,
            file=s3_file_path,
            defaults={
                'filename': scorm_name,
                'status': CourseFile.CourseFileStatus.SUCCESS,
            }
        )

        ScormFile.objects.get_or_create(
            scorm=scorm,
            course_file=course_file,
        )

    def create_module_scorm(self, lesson_data):
        moe_id = lesson_data['id']
        scorm, created = Scorm.objects.get_or_create(
            moe_id=moe_id,
            defaults={
                'course': self.course,
                'name': lesson_data['name'],
                'is_active': False,
            }
        )
        self.modules_map[moe_id] = scorm

        if ScormFile.objects.filter(scorm=scorm).exists():
            self.stdout.write(f"Скорм {lesson_data['name']} уже загружен")
            return

        self.create_scorm_file(lesson_data, scorm)

        if created:
            self.stdout.write(f"Создан скорм-модуль {lesson_data['name']}")

    def create_results(self):
        create_progress = getattr(self, f"create_progress_{self.template}", self.create_progress_default)
        for result in tqdm(self.results):
            student = self.students_map.get(result['user_id'])
            if not student:
                self.stderr.write(f"Студент moe_user_id={result['user_id']} не найден")
                continue
            create_progress(result, student)

        self.stdout.write(f"Перенесли {len(self.results)} прогрессов студентов по модулям")

    def create_progress_default(self, result, student):
        for answer in result['answers'].values():
            for params in answer:
                for problem_id in params.get('theory', {}):
                    module = self.modules_map.get(int(problem_id))
                    if not module:
                        continue

                    module.complete(student)

    @transaction.atomic
    def create_progress_scorm(self, result, student):
        scorm_data = result.get('scorm_data')
        if not scorm_data:
            self.stderr.write(f"Отсутствуют данные по скорму в модуле {result['lesson_id']}")
            return

        scorm = self.modules_map.get(result['lesson_id'])
        if not scorm or not scorm.current_file:
            self.stderr.write(f"Отсутствует скорм-файл в модуле {result['lesson_id']}")
            return

        scorm_student_attempt, created = ScormStudentAttempt.objects.get_or_create(
            scorm=scorm,
            scorm_file=scorm.current_file,
            student=student,
        )

        scorm_resource_student_attempt = ScormResourceStudentAttempt.objects.filter(
            student=student,
            scorm_resource=scorm_student_attempt.scorm_file.resources.first()
        ).first()
        if not scorm_resource_student_attempt:
            self.stderr.write(f"Отсутствует попытка прохождения скорм-модуля {scorm.name} у студента {student}")
            return

        scorm_resource_student_attempt.data = scorm_data
        scorm_resource_student_attempt.save()

    def handle(self, *args, **options):
        with open(options['input'], 'r', encoding='utf-8') as read_file:
            data = json.load(read_file)
        self.template = options['template']

        self.course_data = data['course']
        self.lessons_data = sorted(data['lessons'], key=lambda x: x['order'])
        self.users_data = data['users']
        self.resources = data['resources']
        self.results = data['results']

        if options['show_scorms']:
            for lesson_data in tqdm(self.lessons_data):
                scorm_url = self.get_scorm_url(lesson_data)
                self.stdout.write(scorm_url)

            return

        self.create_course()

        if not options['no_students']:
            post_save.disconnect(receiver=user_post_save_handler, sender=User)
            post_save.disconnect(receiver=staff_profile_post_save_handler, sender=StaffProfile)
            post_save.disconnect(receiver=staff_profile_in_mailing_post_save_handler, sender=StaffProfile)
            post_save.connect(receiver=modified_user_post_save_handler, sender=User)

            self.create_students()

            post_save.disconnect(receiver=modified_user_post_save_handler, sender=User)
            post_save.connect(receiver=staff_profile_in_mailing_post_save_handler, sender=StaffProfile)
            post_save.connect(receiver=staff_profile_post_save_handler, sender=StaffProfile)
            post_save.connect(receiver=user_post_save_handler, sender=User)

        if not options['no_results']:
            self.create_results()
