import base64
from io import BytesIO

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
from cryptography.fernet import Fernet, InvalidToken

from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import PermissionDenied
from django.core.files.base import ContentFile
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, reverse
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.text import slugify
from django.views.generic import FormView, TemplateView, View

from aemo.models import Famille
from aemo.utils import is_ajax
from .models import Archive
from .pdf import ArchivePdf

from .forms import ArchiveFilterForm, ArchiveKeyUploadForm


class ArchiveCreateView(View):

    def post(self, request, *args, **kwargs):
        unite = 'aemo'
        temp = BytesIO()

        famille = get_object_or_404(Famille, pk=kwargs['pk'], archived_at__isnull=True)
        intervenants_list = '/'.join([f"{interv.nom} {interv.prenom[0].upper()}."
                                     for interv in famille.suivi.intervenants.all()])
        ope_list = famille.suivi.ope_referents
        motif_fin = famille.suivi.get_motif_fin_suivi_display()
        pdf = ArchivePdf(temp, famille)

        if not famille.can_be_archived(self.request.user):
            raise PermissionDenied("Vous n'avez pas les droits nécessaires pour accéder à cette page.")

        famille.archived_at = timezone.now()
        pdf.produce()
        filename = f"{unite}/{pdf.get_filename()}"
        temp.seek(0)
        pdf = temp.read()

        # Create a symmetric Fernet key to encrypt the PDF, and encrypt that key with asymmetric RSA key.
        key = Fernet.generate_key()
        fernet = Fernet(key)
        pdf_crypted = fernet.encrypt(pdf)

        with open(settings.CRNE_RSA_PUBLIC_KEY, "rb") as key_file:
            public_key = serialization.load_ssh_public_key(key_file.read())
        padd = padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), label=None)
        fernet_crypted = public_key.encrypt(key, padding=padd)

        nom_famille = famille.nom
        arch = Archive.objects.create(
            nom=nom_famille,
            unite=unite,
            intervenant=intervenants_list,
            ope=", ".join([ope.nom_prenom for ope in ope_list]),
            motif_fin=motif_fin,
            date_debut=famille.suivi.date_debut_suivi,
            date_fin=famille.suivi.date_fin_suivi,
            key=base64.b64encode(fernet_crypted).decode(),
            pdf=None
        )
        arch.pdf.save(filename, ContentFile(pdf_crypted))
        famille.archived_at = timezone.now()
        famille.save()

        famille.anonymiser()

        if is_ajax(request):
            return JsonResponse({'id': famille.pk}, safe=True)

        messages.success(request, f"La famille «{nom_famille}» a bien été archivée.")
        return HttpResponseRedirect(reverse("suivis-termines"))


class ArchiveListView(TemplateView):
    template_name = 'archive/list.html'
    model = Archive

    def dispatch(self, request, *args, **kwargs):
        self.unite = self.kwargs['unite']
        self.filter_form = ArchiveFilterForm(data=request.GET or None)
        return super().dispatch(request, *args, **kwargs)

    def get(self, request, *args, **kwargs):
        if is_ajax(request) and self.unite == 'aemo':
            if self.filter_form.is_bound and self.filter_form.is_valid():
                archives = self.filter_form.filter(Archive.objects.filter(unite=self.unite))
            else:
                archives = Archive.objects.none()
            response = render_to_string(
                template_name='archive/list_partial.html',
                context={
                    'archives': archives,
                    'can_download': request.user.has_perm('aemo.can_archive'),
                }
            )
            return JsonResponse(response, safe=False)
        return super().get(request, *args, **kwargs)

    def get_context_data(self, *args, **kwargs):
        return {
            **super().get_context_data(*args, **kwargs),
            'unite': self.unite,
            'form': self.filter_form,
            'archives': Archive.objects.filter(unite=self.unite),
            'can_download': self.request.user.has_perm('aemo.can_archive'),
        }


class ArchiveDecryptView(PermissionRequiredMixin, FormView):
    form_class = ArchiveKeyUploadForm
    template_name = 'archive/key_upload.html'
    permission_required = 'aemo.can_archive'

    def form_valid(self, form):
        arch = get_object_or_404(Archive, pk=self.kwargs['pk'])
        try:
            with open(arch.pdf.path, "rb") as fh:
                pdf_crypted = fh.read()
        except OSError as err:
            messages.error(self.request, f"Erreur lors de la lecture du document ({str(err)})")
            return HttpResponseRedirect(reverse('archive-list', args=[arch.unite]))

        try:
            private_key_file = self.request.FILES['file'].read()
            private_key = serialization.load_pem_private_key(private_key_file, password=None)
            padd = padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), label=None)
            sim_key = private_key.decrypt(base64.b64decode(arch.key), padding=padd)

            fernet = Fernet(sim_key)
            pdf_content = fernet.decrypt(pdf_crypted)
        except ValueError as err:
            messages.error(self.request, f"Erreur lors de la lecture de la clé ({str(err)})")
            return HttpResponseRedirect(reverse('archive-list', args=[arch.unite]))
        except InvalidToken:
            messages.error(self.request, "Erreur lors du déchiffrement")
            return HttpResponseRedirect(reverse('archive-list', args=[arch.unite]))

        filename = f"{slugify(arch.nom)}.pdf"
        response = HttpResponse(pdf_content, content_type='application/pdf')
        response['Content-Disposition'] = "attachment; filename=%s" % filename
        return response
