"""passbook OTP Views"""
from base64 import b32encode
from binascii import unhexlify
from logging import getLogger

from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import Http404, HttpRequest, HttpResponse
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.translation import ugettext as _
from django.views import View
from django.views.generic import FormView, TemplateView
from django_otp.plugins.otp_static.models import StaticDevice, StaticToken
from django_otp.plugins.otp_totp.models import TOTPDevice
from qrcode import make
from qrcode.image.svg import SvgPathImage

from passbook.lib.boilerplate import NeverCacheMixin
from passbook.lib.config import CONFIG
from passbook.otp.forms import OTPSetupForm
from passbook.otp.utils import otpauth_url

OTP_SESSION_KEY = 'passbook_otp_key'
OTP_SETTING_UP_KEY = 'passbook_otp_setup'
LOGGER = getLogger(__name__)

class UserSettingsView(LoginRequiredMixin, TemplateView):
    """View for user settings to control OTP"""

    template_name = 'otp/user_settings.html'

    # TODO: Check if OTP Factor exists and applies to user
    def get_context_data(self, **kwargs):
        kwargs = super().get_context_data(**kwargs)
        static = StaticDevice.objects.filter(user=self.request.user, confirmed=True)
        if static.exists():
            kwargs['static_tokens'] = StaticToken.objects.filter(device=static.first()) \
                                        .order_by('token')
        totp_devices = TOTPDevice.objects.filter(user=self.request.user, confirmed=True)
        kwargs['state'] = totp_devices.exists() and static.exists()
        return kwargs

class DisableView(LoginRequiredMixin, TemplateView):
    """Disable TOTP for user"""
    # TODO: Use Django DeleteView with custom delete?
    # def
    # # Delete all the devices for user
    # static = get_object_or_404(StaticDevice, user=request.user, confirmed=True)
    # static_tokens = StaticToken.objects.filter(device=static).order_by('token')
    # totp = TOTPDevice.objects.filter(user=request.user, confirmed=True)
    # static.delete()
    # totp.delete()
    # for token in static_tokens:
    #     token.delete()
    # messages.success(request, 'Successfully disabled TOTP')
    # # Create event with email notification
    # # Event.create(
    # #     user=request.user,
    # #     message=_('You disabled TOTP.'),
    # #     current=True,
    # #     request=request,
    # #     send_notification=True)
    # return redirect(reverse('passbook_core:overview'))


class EnableView(LoginRequiredMixin, FormView):
    """View to set up OTP"""

    title = _('Set up OTP')
    form_class = OTPSetupForm
    template_name = 'login/form.html'

    totp_device = None
    static_device = None

    # TODO: Check if OTP Factor exists and applies to user
    def get_context_data(self, **kwargs):
        kwargs['config'] = CONFIG.get('passbook')
        kwargs['is_login'] = True
        kwargs['title'] = _('Configue OTP')
        kwargs['primary_action'] = _('Setup')
        return super().get_context_data(**kwargs)

    def dispatch(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
        # Check if user has TOTP setup already
        finished_totp_devices = TOTPDevice.objects.filter(user=request.user, confirmed=True)
        finished_static_devices = StaticDevice.objects.filter(user=request.user, confirmed=True)
        if finished_totp_devices.exists() and finished_static_devices.exists():
            messages.error(request, _('You already have TOTP enabled!'))
            del request.session[OTP_SETTING_UP_KEY]
            return redirect('passbook_otp:otp-user-settings')
        request.session[OTP_SETTING_UP_KEY] = True
        # Check if there's an unconfirmed device left to set up
        totp_devices = TOTPDevice.objects.filter(user=request.user, confirmed=False)
        if not totp_devices.exists():
            # Create new TOTPDevice and save it, but not confirm it
            self.totp_device = TOTPDevice(user=request.user, confirmed=False)
            self.totp_device.save()
        else:
            self.totp_device = totp_devices.first()

        # Check if we have a static device already
        static_devices = StaticDevice.objects.filter(user=request.user, confirmed=False)
        if not static_devices.exists():
            # Create new static device and some codes
            self.static_device = StaticDevice(user=request.user, confirmed=False)
            self.static_device.save()
            # Create 9 tokens and save them
            # TODO: Send static tokens via E-Mail
            for _counter in range(0, 9):
                token = StaticToken(device=self.static_device, token=StaticToken.random_token())
                token.save()
        else:
            self.static_device = static_devices.first()

        # Somehow convert the generated key to base32 for the QR code
        rawkey = unhexlify(self.totp_device.key.encode('ascii'))
        request.session[OTP_SESSION_KEY] = b32encode(rawkey).decode("utf-8")
        return super().dispatch(request, *args, **kwargs)

    def get_form(self, form_class=None):
        form = super().get_form(form_class=form_class)
        form.device = self.totp_device
        form.fields['qr_code'].initial = reverse('passbook_otp:otp-qr')
        tokens = [(x.token, x.token) for x in self.static_device.token_set.all()]
        form.fields['tokens'].choices = tokens
        return form

    def form_valid(self, form):
        # Save device as confirmed
        LOGGER.debug("Saved OTP Devices")
        self.totp_device.confirmed = True
        self.totp_device.save()
        self.static_device.confirmed = True
        self.static_device.save()
        del self.request.session[OTP_SETTING_UP_KEY]
        # Create event with email notification
        # TODO: Create Audit Log entry
        # Event.create(
        #     user=self.request.user,
        #     message=_('You activated TOTP.'),
        #     current=True,
        #     request=self.request,
        #     send_notification=True)
        return redirect('passbook_otp:otp-user-settings')

class QRView(NeverCacheMixin, View):
    """View returns an SVG image with the OTP token information"""

    def get(self, request: HttpRequest) -> HttpResponse:
        """View returns an SVG image with the OTP token information"""
        # Get the data from the session
        try:
            key = request.session[OTP_SESSION_KEY]
        except KeyError:
            raise Http404

        url = otpauth_url(accountname=request.user.username, secret=key)
        # Make and return QR code
        img = make(url, image_factory=SvgPathImage)
        resp = HttpResponse(content_type='image/svg+xml; charset=utf-8')
        img.save(resp)
        return resp