# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2019 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import datetime
import json

from dateutil import parser as date_parser
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.db.models import Q
from django.forms import models as model_forms
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.timezone import make_aware
from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, DeleteView, DetailView, FormView, ListView, UpdateView, View

from ..utils import ImportSiteError, export_site, get_trusted_services, import_site
from ..views import GenericConnectorMixin
from .forms import AccessRightForm, ApiUserForm, AvailabilityParametersForm, ImportSiteForm
from .models import AccessRight, ApiUser, Job, LoggingParameters, ResourceStatus


class ResourceView(DetailView):
    template_name = 'passerelle/base/view.html'

    def get_object(self, queryset=None):
        try:
            obj = self.model.objects.get_subclass(slug=self.kwargs['slug'])
        except ObjectDoesNotExist:
            raise Http404
        if obj.is_accessible_by(self.request):
            return obj
        raise PermissionDenied

    def get_context_data(self, slug=None, **kwargs):
        context = super().get_context_data(**kwargs)
        context['site_base_uri'] = '%s://%s' % (
            'https' if self.request.is_secure() else 'http',
            self.request.get_host(),
        )
        context['absolute_uri'] = '%s%s' % (context['site_base_uri'], self.request.path)
        return context


class ApiUserCreateView(CreateView):
    model = ApiUser
    form_class = ApiUserForm
    template_name = 'passerelle/manage/apiuser_form.html'

    def get_success_url(self):
        return reverse('apiuser-list')


class ApiUserUpdateView(UpdateView):
    model = ApiUser
    form_class = ApiUserForm
    template_name = 'passerelle/manage/apiuser_form.html'

    def get_success_url(self):
        return reverse('apiuser-list')


class ApiUserDeleteView(DeleteView):
    model = ApiUser
    template_name = 'passerelle/manage/apiuser_confirm_delete.html'

    def get_success_url(self):
        return reverse('apiuser-list')


class ApiUserListView(ListView):
    model = ApiUser
    template_name = 'passerelle/manage/apiuser_list.html'
    paginate_by = 25
    ordering = 'id'

    def get_context_data(self, slug=None, **kwargs):
        context = super().get_context_data(**kwargs)
        context['trusted_services'] = get_trusted_services()
        return context


class AccessRightDeleteView(DeleteView):
    model = AccessRight
    template_name = 'passerelle/manage/accessright_confirm_delete.html'

    def get_object(self):
        object = super().get_object()
        self.resource = object.resource
        return object

    def get_success_url(self):
        return self.resource.get_absolute_url()


class AccessRightCreateView(CreateView):
    model = AccessRight
    form_class = AccessRightForm
    template_name = 'passerelle/manage/accessright_form.html'

    def get_initial(self):
        d = self.initial.copy()
        d['codename'] = self.kwargs.get('codename')
        d['resource_type'] = self.kwargs.get('resource_type')
        d['resource_pk'] = self.kwargs.get('resource_pk')
        return d

    def form_valid(self, form):
        if not form.cleaned_data['apiuser'].key and not form.allow_open_access:
            form.add_confirmation_checkbox()
            return self.form_invalid(form)
        return super().form_valid(form)

    def get_success_url(self):
        return self.object.resource.get_absolute_url()


class LoggingParametersUpdateView(FormView):
    template_name = 'passerelle/manage/logging_parameters_form.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['connector'] = self.get_resource()
        return context

    def get_form_class(self):
        form_class = model_forms.modelform_factory(
            LoggingParameters,
            fields=[
                'log_level',
                'trace_emails',
                'requests_max_size',
                'responses_max_size',
                'log_retention_days',
            ],
        )
        form_class.base_fields['trace_emails'].widget.attrs['rows'] = '3'
        return form_class

    def get_initial(self):
        d = self.initial.copy()
        d['resource_type'] = self.kwargs['resource_type']
        d['resource_pk'] = self.kwargs['resource_pk']
        parameters = self.get_resource().logging_parameters
        d['log_level'] = parameters.log_level
        d['trace_emails'] = parameters.trace_emails
        d['requests_max_size'] = parameters.requests_max_size
        d['responses_max_size'] = parameters.responses_max_size
        d['log_retention_days'] = parameters.log_retention_days
        return d

    def get_resource(self):
        content_type = ContentType.objects.get_for_id(self.kwargs['resource_type'])
        return content_type.model_class().objects.get(pk=self.kwargs['resource_pk'])

    def get_success_url(self):
        return self.get_resource().get_absolute_url()

    def form_valid(self, form):
        parameters = self.get_resource().logging_parameters
        parameters.log_level = form.cleaned_data['log_level']
        parameters.trace_emails = form.cleaned_data['trace_emails']
        parameters.requests_max_size = form.cleaned_data['requests_max_size']
        parameters.responses_max_size = form.cleaned_data['responses_max_size']
        parameters.log_retention_days = form.cleaned_data['log_retention_days']
        parameters.save()
        return super().form_valid(form)


class ManageAvailabilityView(UpdateView):
    template_name = 'passerelle/manage/manage_availability_form.html'
    form_class = AvailabilityParametersForm

    def get_resource(self):
        if not hasattr(self, '_resource'):
            content_type = ContentType.objects.get_for_id(self.kwargs['resource_type'])
            self._resource = content_type.model_class().objects.get(pk=self.kwargs['resource_pk'])
        return self._resource

    def get_object(self, queryset=None):
        return self.get_resource().availability_parameters

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['connector'] = self.get_resource()
        context['availability_status'] = self.get_resource().get_availability_status()
        return context

    def get_success_url(self):
        return self.get_resource().get_absolute_url()

    def form_valid(self, form):
        resource = self.get_resource()

        # if availability check is disabled resource is forced to up to activate logs
        if not form.instance.run_check and resource.down():
            resource_type = ContentType.objects.get_for_model(resource)
            ResourceStatus(
                resource_type=resource_type, resource_pk=self.kwargs['resource_pk'], status='up', message=''
            ).save()

        # log changes to notification delays
        if 'notification_delays' in form.changed_data:
            resource.logger.info('availability checks delays set to %s', form.instance.notification_delays)

        # log changes to run_check, if enabled immediately check for availability
        if 'run_check' in form.changed_data:
            resource.logger.info(
                'availability checks %s', 'enabled' if form.instance.run_check else 'disabled'
            )
            if form.instance.run_check:
                resource.availability()

        return super().form_valid(form)


class GenericViewJobsConnectorView(GenericConnectorMixin, ListView):
    template_name = 'passerelle/manage/service_jobs.html'
    paginate_by = 25

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        connector = self.get_object()
        context['object'] = connector
        context['query'] = self.request.GET.get('q') or ''
        if self.request.GET.get('job_id'):
            try:
                resource_type = ContentType.objects.get_for_model(connector)
                context['job_target'] = Job.objects.get(
                    resource_type=resource_type, resource_pk=connector.pk, pk=self.request.GET['job_id']
                )
            except (ValueError, Job.DoesNotExist):
                pass
        return context

    def get_object(self):
        return self.model.objects.get(slug=self.kwargs['slug'])

    def get_queryset(self):
        connector = self.get_object()
        qs = connector.jobs_set().order_by('-creation_timestamp')
        query = self.request.GET.get('q')
        if query:
            try:
                date = date_parser.parse(query, dayfirst=True)
            except Exception:
                qs = qs.filter(method_name__icontains=query)
            else:
                date = make_aware(date)
                if date.hour == 0 and date.minute == 0 and date.second == 0:
                    # just a date: display all jobs for that date
                    max_date = date + datetime.timedelta(days=1)
                    qs = qs.filter(
                        Q(
                            creation_timestamp__gte=date,
                            creation_timestamp__lte=date + datetime.timedelta(days=1),
                        )
                        | Q(
                            update_timestamp__gte=date,
                            update_timestamp__lte=date + datetime.timedelta(days=1),
                        )
                        | Q(done_timestamp__gte=date, done_timestamp__lte=date + datetime.timedelta(days=1))
                    )
                elif date.second == 0:
                    # without seconds: display all jobs in this minute
                    max_date = date + datetime.timedelta(seconds=60)
                else:
                    # display all jobs in the same second
                    max_date = date + datetime.timedelta(seconds=1)

                qs = qs.filter(
                    Q(creation_timestamp__gte=date, creation_timestamp__lte=max_date)
                    | Q(update_timestamp__gte=date, update_timestamp__lte=max_date)
                    | Q(update_timestamp__gte=date, update_timestamp__lte=max_date)
                )

        return qs


class GenericJobView(GenericConnectorMixin, DetailView):
    template_name = 'passerelle/manage/job.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        try:
            context['job'] = Job.objects.get(pk=self.kwargs['job_pk'])
        except Job.DoesNotExist:
            raise Http404()
        return context


class GenericRestartJobView(GenericConnectorMixin, View):
    def get(self, request, *args, **kwargs):
        connector = get_object_or_404(self.model, slug=kwargs['slug'])
        resource_type = ContentType.objects.get_for_model(connector)
        job = get_object_or_404(
            Job,
            pk=self.kwargs['job_pk'],
            resource_type=resource_type,
            resource_pk=connector.pk,
            status='failed',
        )
        job.restart()
        return HttpResponseRedirect(
            reverse('view-jobs-connector', kwargs={'connector': kwargs['connector'], 'slug': kwargs['slug']})
        )


class ImportSiteView(FormView):
    template_name = 'passerelle/manage/import_site.html'
    form_class = ImportSiteForm

    def get_success_url(self):
        return reverse('manage-home')

    def form_valid(self, form):
        try:
            site_json = json.loads(self.request.FILES['site_json'].read())
        except ValueError:
            form.add_error('site_json', _('File is not in the expected JSON format.'))
            return self.form_invalid(form)

        try:
            import_site(site_json, overwrite=True, import_users=form.cleaned_data['import_users'])
        except ImportSiteError as e:
            form.add_error('site_json', e)
            return self.form_invalid(form)
        return super().form_valid(form)


class ExportSiteView(View):
    def get(self, request, *args, **kwargs):
        response = HttpResponse(content_type='application/json')
        today = datetime.date.today()
        response['Content-Disposition'] = 'attachment; filename="export_connectors_{}.json"'.format(
            today.strftime('%Y%m%d')
        )
        json.dump(export_site(), response, indent=2)
        return response
