from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Field, HTML
from django import forms
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Q
from django.http import HttpRequest, HttpResponseBadRequest, JsonResponse
from django.shortcuts import render, redirect
from django.views.generic import CreateView, UpdateView, DeleteView, FormView
from django.views.generic.edit import FormMixin
from django.conf import settings
from django.core.paginator import Paginator
from YtManagerApp.management.videos import get_videos
from YtManagerApp.management.appconfig import appconfig
from YtManagerApp.models import Subscription, SubscriptionFolder, VIDEO_ORDER_CHOICES, VIDEO_ORDER_MAPPING
from YtManagerApp.utils import youtube, subscription_file_parser
from YtManagerApp.views.controls.modal import ModalMixin

import logging


class VideoFilterForm(forms.Form):
    CHOICES_SHOW_WATCHED = (
        ('y', 'Watched'),
        ('n', 'Not watched'),
        ('all', '(All)')
    )

    CHOICES_SHOW_DOWNLOADED = (
        ('y', 'Downloaded'),
        ('n', 'Not downloaded'),
        ('all', '(All)')
    )

    MAPPING_SHOW = {
        'y': True,
        'n': False,
        'all': None
    }

    CHOICES_RESULT_COUNT = (
        (25, 25),
        (50, 50),
        (100, 100),
        (200, 200)
    )

    query = forms.CharField(label='', required=False)
    sort = forms.ChoiceField(label='Sort:', choices=VIDEO_ORDER_CHOICES, initial='newest')
    show_watched = forms.ChoiceField(label='Show only: ', choices=CHOICES_SHOW_WATCHED, initial='all')
    show_downloaded = forms.ChoiceField(label='', choices=CHOICES_SHOW_DOWNLOADED, initial='all')
    subscription_id = forms.IntegerField(
        required=False,
        widget=forms.HiddenInput()
    )
    folder_id = forms.IntegerField(
        required=False,
        widget=forms.HiddenInput()
    )
    page = forms.IntegerField(
        required=False,
        widget=forms.HiddenInput()
    )
    results_per_page = forms.ChoiceField(label='Results per page: ', choices=CHOICES_RESULT_COUNT, initial=50)

    def __init__(self, data=None):
        super().__init__(data, auto_id='form_video_filter_%s')
        self.helper = FormHelper()
        self.helper.form_id = 'form_video_filter'
        self.helper.form_class = 'form-inline'
        self.helper.form_method = 'POST'
        self.helper.form_action = 'ajax_get_videos'
        self.helper.field_class = 'mr-1'
        self.helper.label_class = 'ml-2 mr-1 no-asterisk'

        self.helper.layout = Layout(
            Field('query', placeholder='Search'),
            'sort',
            'show_watched',
            'show_downloaded',
            'subscription_id',
            'folder_id',
            'page',
            'results_per_page'
        )

    def clean_sort(self):
        data = self.cleaned_data['sort']
        return VIDEO_ORDER_MAPPING[data]

    def clean_show_downloaded(self):
        data = self.cleaned_data['show_downloaded']
        return VideoFilterForm.MAPPING_SHOW[data]

    def clean_show_watched(self):
        data = self.cleaned_data['show_watched']
        return VideoFilterForm.MAPPING_SHOW[data]


def __tree_folder_id(fd_id):
    if fd_id is None:
        return '#'
    return 'folder' + str(fd_id)


def __tree_sub_id(sub_id):
    if sub_id is None:
        return '#'
    return 'sub' + str(sub_id)


def index(request: HttpRequest):

    if not appconfig.initialized:
        return redirect('first_time_0')

    context = {
        'config_errors': settings.CONFIG_ERRORS,
        'config_warnings': settings.CONFIG_WARNINGS,
    }
    if request.user.is_authenticated:
        context.update({
            'filter_form': VideoFilterForm(),
        })
        return render(request, 'YtManagerApp/index.html', context)
    else:
        return render(request, 'YtManagerApp/index_unauthenticated.html', context)


@login_required
def ajax_get_tree(request: HttpRequest):

    def visit(node):
        if isinstance(node, SubscriptionFolder):
            return {
                "id": __tree_folder_id(node.id),
                "text": node.name,
                "type": "folder",
                "state": {"opened": True},
                "parent": __tree_folder_id(node.parent_id)
            }
        elif isinstance(node, Subscription):
            return {
                "id": __tree_sub_id(node.id),
                "type": "sub",
                "text": node.name,
                "icon": node.thumbnail,
                "parent": __tree_folder_id(node.parent_folder_id)
            }

    result = SubscriptionFolder.traverse(None, request.user, visit)
    return JsonResponse(result, safe=False)


@login_required
def ajax_get_videos(request: HttpRequest):
    if request.method == 'POST':
        form = VideoFilterForm(request.POST)
        if form.is_valid():
            videos = get_videos(
                user=request.user,
                sort_order=form.cleaned_data['sort'],
                query=form.cleaned_data['query'],
                subscription_id=form.cleaned_data['subscription_id'],
                folder_id=form.cleaned_data['folder_id'],
                only_watched=form.cleaned_data['show_watched'],
                only_downloaded=form.cleaned_data['show_downloaded']
            )

            paginator = Paginator(videos, form.cleaned_data['results_per_page'])
            videos = paginator.get_page(form.cleaned_data['page'])

            context = {
                'videos': videos
            }

            return render(request, 'YtManagerApp/index_videos.html', context)

    return HttpResponseBadRequest()


class SubscriptionFolderForm(forms.ModelForm):
    class Meta:
        model = SubscriptionFolder
        fields = ['name', 'parent']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_tag = False

    def clean_name(self):
        name = self.cleaned_data['name']
        return name.strip()

    def clean(self):
        cleaned_data = super().clean()
        name = cleaned_data.get('name')
        parent = cleaned_data.get('parent')

        # Check name is unique in parent folder
        args_id = []
        if self.instance is not None:
            args_id.append(~Q(id=self.instance.id))

        if SubscriptionFolder.objects.filter(parent=parent, name__iexact=name, *args_id).count() > 0:
            raise forms.ValidationError(
                'A folder with the same name already exists in the given parent directory!', code='already_exists')

        # Check for cycles
        if self.instance is not None:
            self.__test_cycles(parent)

    def __test_cycles(self, new_parent):
        visited = [self.instance.id]
        current = new_parent
        while current is not None:
            if current.id in visited:
                raise forms.ValidationError('Selected parent would create a parenting cycle!', code='parenting_cycle')
            visited.append(current.id)
            current = current.parent


class CreateFolderModal(LoginRequiredMixin, ModalMixin, CreateView):
    template_name = 'YtManagerApp/controls/folder_create_modal.html'
    form_class = SubscriptionFolderForm

    def form_valid(self, form):
        form.instance.user = self.request.user
        return super().form_valid(form)


class UpdateFolderModal(LoginRequiredMixin, ModalMixin, UpdateView):
    template_name = 'YtManagerApp/controls/folder_update_modal.html'
    model = SubscriptionFolder
    form_class = SubscriptionFolderForm


class DeleteFolderForm(forms.Form):
    keep_subscriptions = forms.BooleanField(required=False, initial=False, label="Keep subscriptions")


class DeleteFolderModal(LoginRequiredMixin, ModalMixin, FormMixin, DeleteView):
    template_name = 'YtManagerApp/controls/folder_delete_modal.html'
    model = SubscriptionFolder
    form_class = DeleteFolderForm

    def __init__(self, *args, **kwargs):
        self.object = None
        super().__init__(*args, **kwargs)

    def delete(self, request, *args, **kwargs):
        self.object = self.get_object()
        form = self.get_form()
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

    def form_valid(self, form):
        self.object.delete_folder(keep_subscriptions=form.cleaned_data['keep_subscriptions'])
        return super().form_valid(form)


class CreateSubscriptionForm(forms.ModelForm):
    playlist_url = forms.URLField(label='Playlist/Channel URL')

    class Meta:
        model = Subscription
        fields = ['parent_folder', 'auto_download',
                  'download_limit', 'download_order', "automatically_delete_watched"]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.yt_api = youtube.YoutubeAPI.build_public()
        self.helper = FormHelper()
        self.helper.form_tag = False
        self.helper.layout = Layout(
            'playlist_url',
            'parent_folder',
            HTML('<hr>'),
            HTML('<h5>Download configuration overloads</h5>'),
            'auto_download',
            'download_limit',
            'download_order',
            'automatically_delete_watched'
        )

    def clean_playlist_url(self):
        playlist_url: str = self.cleaned_data['playlist_url']
        try:
            parsed_url = self.yt_api.parse_url(playlist_url)
        except youtube.InvalidURL as e:
            raise forms.ValidationError(str(e))

        is_playlist = 'playlist' in parsed_url
        is_channel = parsed_url['type'] in ('channel', 'user', 'channel_custom')

        if not is_channel and not is_playlist:
            raise forms.ValidationError('The given URL must link to a channel or a playlist!')

        return playlist_url


class CreateSubscriptionModal(LoginRequiredMixin, ModalMixin, CreateView):
    template_name = 'YtManagerApp/controls/subscription_create_modal.html'
    form_class = CreateSubscriptionForm

    def form_valid(self, form):
        form.instance.user = self.request.user
        api = youtube.YoutubeAPI.build_public()
        try:
            form.instance.fetch_from_url(form.cleaned_data['playlist_url'], api)
        except youtube.InvalidURL as e:
            return self.modal_response(form, False, str(e))
        except ValueError as e:
            return self.modal_response(form, False, str(e))
        # except youtube.YoutubeUserNotFoundException:
        #     return self.modal_response(
        #         form, False, 'Could not find an user based on the given URL. Please verify that the URL is correct.')
        # except youtube.YoutubePlaylistNotFoundException:
        #     return self.modal_response(
        #         form, False, 'Could not find a playlist based on the given URL. Please verify that the URL is correct.')
        # except youtube.YoutubeException as e:
        #     return self.modal_response(
        #         form, False, str(e))
        # except youtube.APIError as e:
        #     return self.modal_response(
        #         form, False, 'An error occurred while communicating with the YouTube API: ' + str(e))

        return super().form_valid(form)


class UpdateSubscriptionForm(forms.ModelForm):
    class Meta:
        model = Subscription
        fields = ['name', 'parent_folder', 'auto_download',
                  'download_limit', 'download_order', "automatically_delete_watched", 'last_synchronised']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_tag = False
        self.helper.layout = Layout(
            'name',
            'parent_folder',
            HTML('<hr>'),
            HTML('<h5>Download configuration overloads</h5>'),
            'auto_download',
            'download_limit',
            'download_order',
            'automatically_delete_watched',
            Field('last_synchronised', readonly=True)
        )


class UpdateSubscriptionModal(LoginRequiredMixin, ModalMixin, UpdateView):
    template_name = 'YtManagerApp/controls/subscription_update_modal.html'
    model = Subscription
    form_class = UpdateSubscriptionForm


class DeleteSubscriptionForm(forms.Form):
    keep_downloaded_videos = forms.BooleanField(required=False, initial=False, label="Keep downloaded videos")


class DeleteSubscriptionModal(LoginRequiredMixin, ModalMixin, FormMixin, DeleteView):
    template_name = 'YtManagerApp/controls/subscription_delete_modal.html'
    model = Subscription
    form_class = DeleteSubscriptionForm

    def __init__(self, *args, **kwargs):
        self.object = None
        super().__init__(*args, **kwargs)

    def delete(self, request, *args, **kwargs):
        self.object = self.get_object()
        form = self.get_form()
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

    def form_valid(self, form):
        self.object.delete_subscription(keep_downloaded_videos=form.cleaned_data['keep_downloaded_videos'])
        return super().form_valid(form)


class ImportSubscriptionsForm(forms.Form):
    TRUE_FALSE_CHOICES = (
        (None, '(default)'),
        (True, 'Yes'),
        (False, 'No')
    )

    VIDEO_ORDER_CHOICES_WITH_EMPTY = (
        ('', '(default)'),
        *VIDEO_ORDER_CHOICES,
    )

    file = forms.FileField(label='File to import',
                           help_text='Supported file types: OPML, subscription list')
    parent_folder = forms.ModelChoiceField(SubscriptionFolder.objects, required=False)
    auto_download = forms.ChoiceField(choices=TRUE_FALSE_CHOICES, required=False)
    download_limit = forms.IntegerField(required=False)
    download_order = forms.ChoiceField(choices=VIDEO_ORDER_CHOICES_WITH_EMPTY, required=False)
    automatically_delete_watched = forms.ChoiceField(choices=TRUE_FALSE_CHOICES, required=False)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.yt_api = youtube.YoutubeAPI.build_public()
        self.helper = FormHelper()
        self.helper.form_tag = False
        self.helper.layout = Layout(
            'file',
            'parent_folder',
            HTML('<hr>'),
            HTML('<h5>Download configuration overloads</h5>'),
            'auto_download',
            'download_limit',
            'download_order',
            'automatically_delete_watched'
        )

    def __clean_empty_none(self, name: str):
        data = self.cleaned_data[name]
        if isinstance(data, str) and len(data) == 0:
            return None
        return data

    def __clean_boolean(self, name: str):
        data = self.cleaned_data[name]
        if isinstance(data, str) and len(data) == 0:
            return None
        if isinstance(data, str):
            return data == 'True'
        return data

    def clean_auto_download(self):
        return self.__clean_boolean('auto_download')

    def clean_automatically_delete_watched(self):
        return self.__clean_boolean('automatically_delete_watched')

    def clean_download_order(self):
        return self.__clean_empty_none('download_order')


class ImportSubscriptionsModal(LoginRequiredMixin, ModalMixin, FormView):
    template_name = 'YtManagerApp/controls/subscriptions_import_modal.html'
    form_class = ImportSubscriptionsForm

    def form_valid(self, form):
        file = form.cleaned_data['file']

        # Parse file
        try:
            url_list = list(subscription_file_parser.parse(file))
        except subscription_file_parser.FormatNotSupportedError:
            return super().modal_response(form, success=False,
                                          error_msg="The file could not be parsed! "
                                                    "Possible problems: format not supported, file is malformed.")

        print(form.cleaned_data)

        # Create subscriptions
        api = youtube.YoutubeAPI.build_public()
        for url in url_list:
            sub = Subscription()
            sub.user = self.request.user
            sub.parent_folder = form.cleaned_data['parent_folder']
            sub.auto_download = form.cleaned_data['auto_download']
            sub.download_limit = form.cleaned_data['download_limit']
            sub.download_order = form.cleaned_data['download_order']
            sub.automatically_delete_watched = form.cleaned_data["automatically_delete_watched"]
            try:
                sub.fetch_from_url(url, api)
            except Exception as e:
                logging.error("Import subscription error - error processing URL %s: %s", url, e)
                continue

            sub.save()

        return super().form_valid(form)