# -*- coding: utf-8 -*-
import json

from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.views import LogoutView as LogoutViewBase
from django.contrib.messages.views import SuccessMessageMixin
from django.core.exceptions import BadRequest
from django.forms import Form
from django.http import HttpResponseForbidden
from django.middleware.csrf import REASON_BAD_ORIGIN
from django.shortcuts import redirect, render
from django.template import loader
from django.urls import reverse, reverse_lazy
from django.utils import translation
from django.utils.decorators import method_decorator
from django.utils.text import format_lazy
from django.utils.translation import gettext as _, gettext_lazy
from django.views import csrf
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.http import require_POST
from django.views.generic import View
from django.views.generic.base import TemplateView, RedirectView
from django.views.generic.detail import BaseDetailView
from django.views.generic.edit import (
    CreateView,
    DeleteView,
    FormMixin,
    SingleObjectTemplateResponseMixin,
    UpdateView,
)
from django.views.i18n import set_language

from axes.utils import reset
from django_filters.views import FilterView

from babybuddy import forms
from babybuddy.mixins import LoginRequiredMixin, PermissionRequiredMixin, StaffOnlyMixin


def csrf_failure(request, reason=""):
    """
    Overrides the 403 CSRF failure template for bad origins in order to provide more
    userful information about how to resolve the issue.
    """

    if (
        "HTTP_ORIGIN" in request.META
        and reason == REASON_BAD_ORIGIN % request.META["HTTP_ORIGIN"]
    ):
        context = {
            "title": _("Forbidden"),
            "main": _("CSRF verification failed. Request aborted."),
            "reason": reason,
            "origin": request.META["HTTP_ORIGIN"],
        }
        template = loader.get_template("error/403_csrf_bad_origin.html")
        return HttpResponseForbidden(template.render(context), content_type="text/html")

    return csrf.csrf_failure(request, reason, "403_csrf.html")


class RootRouter(LoginRequiredMixin, RedirectView):
    """
    Redirects to the site dashboard.
    """

    def get_redirect_url(self, *args, **kwargs):
        self.url = reverse("dashboard:dashboard")
        return super(RootRouter, self).get_redirect_url(self, *args, **kwargs)


class BabyBuddyFilterView(FilterView):
    """
    Disables "strictness" for django-filter. It is unclear from the
    documentation exactly what this does...
    """

    # TODO Figure out the correct way to use this.
    strict = False

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        children = {o.child for o in context["object_list"] if hasattr(o, "child")}
        if len(children) == 1:
            context["unique_child"] = True
        return context


@method_decorator(csrf_protect, name="dispatch")
@method_decorator(never_cache, name="dispatch")
@method_decorator(require_POST, name="dispatch")
class LogoutView(LogoutViewBase):
    pass


class UserList(StaffOnlyMixin, BabyBuddyFilterView):
    model = get_user_model()
    template_name = "babybuddy/user_list.html"
    ordering = "username"
    paginate_by = 10
    filterset_fields = ("username", "first_name", "last_name", "email")


class UserAdd(StaffOnlyMixin, PermissionRequiredMixin, SuccessMessageMixin, CreateView):
    model = get_user_model()
    template_name = "babybuddy/user_form.html"
    permission_required = ("admin.add_user",)
    form_class = forms.UserAddForm
    success_url = reverse_lazy("babybuddy:user-list")
    success_message = gettext_lazy("User %(username)s added!")


class UserUpdate(
    StaffOnlyMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView
):
    model = get_user_model()
    template_name = "babybuddy/user_form.html"
    permission_required = ("admin.change_user",)
    form_class = forms.UserUpdateForm
    success_url = reverse_lazy("babybuddy:user-list")
    success_message = gettext_lazy("User %(username)s updated.")


class UserUnlock(
    StaffOnlyMixin,
    PermissionRequiredMixin,
    SuccessMessageMixin,
    FormMixin,
    SingleObjectTemplateResponseMixin,
    BaseDetailView,
):
    model = get_user_model()
    template_name = "babybuddy/user_confirm_unlock.html"
    permission_required = ("admin.change_user",)
    form_class = Form
    success_message = gettext_lazy("User unlocked.")

    def post(self, request, *args, **kwargs):
        user = self.get_object()
        form = self.get_form()
        if form.is_valid():
            reset(username=user.username)
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

    def get_success_url(self):
        return reverse("babybuddy:user-update", kwargs={"pk": self.kwargs["pk"]})


class UserDelete(
    StaffOnlyMixin, PermissionRequiredMixin, DeleteView, SuccessMessageMixin
):
    model = get_user_model()
    template_name = "babybuddy/user_confirm_delete.html"
    permission_required = ("admin.delete_user",)
    success_url = reverse_lazy("babybuddy:user-list")

    def get_success_message(self, cleaned_data):
        return format_lazy(gettext_lazy("User {user} deleted."), user=self.get_object())


class UserPassword(LoginRequiredMixin, View):
    """
    Handles user password changes.
    """

    form_class = forms.UserPasswordForm
    template_name = "babybuddy/user_password_form.html"

    def get(self, request):
        return render(
            request, self.template_name, {"form": self.form_class(request.user)}
        )

    def post(self, request):
        form = PasswordChangeForm(request.user, request.POST)
        if form.is_valid():
            user = form.save()
            update_session_auth_hash(request, user)
            messages.success(request, _("Password updated."))
        return render(request, self.template_name, {"form": form})


def handle_api_regenerate_request(request) -> bool:
    """
    Checks if the current request contains a request to update the API key
    and if it does, updeates the API key.

    Returns True, if the API-key regenerate request was detected and handled.
    """

    if request.POST.get("api_key_regenerate"):
        request.user.settings.api_key(reset=True)
        messages.success(request, _("User API key regenerated."))
        return True
    return False


class UserSettings(LoginRequiredMixin, View):
    """
    Handles both the User and Settings models.
    Based on this SO answer: https://stackoverflow.com/a/45056835.
    """

    form_user_class = forms.UserForm
    form_settings_class = forms.UserSettingsForm
    template_name = "babybuddy/user_settings_form.html"

    def get(self, request):
        settings = request.user.settings

        return render(
            request,
            self.template_name,
            {
                "form_user": self.form_user_class(instance=request.user),
                "form_settings": self.form_settings_class(instance=settings),
            },
        )

    def post(self, request):
        if handle_api_regenerate_request(request):
            return redirect("babybuddy:user-settings")

        form_user = self.form_user_class(instance=request.user, data=request.POST)
        form_settings = self.form_settings_class(
            instance=request.user.settings, data=request.POST
        )
        if form_user.is_valid() and form_settings.is_valid():
            user = form_user.save(commit=False)
            user_settings = form_settings.save(commit=False)
            user.settings = user_settings
            user.save()
            translation.activate(user.settings.language)
            messages.success(request, _("Settings saved!"))
            translation.deactivate()
            return set_language(request)
        return render(
            request,
            self.template_name,
            {"user_form": form_user, "settings_form": form_settings},
        )


class UserAddDevice(LoginRequiredMixin, View):
    form_user_class = forms.UserForm
    template_name = "babybuddy/user_add_device.html"
    qr_code_template = "babybuddy/login_qr_code.txt"

    def get(self, request):
        # Assemble qr_code json-data. For Home Assistant ingress support, we
        # also need to extract the ingress_session token to allow an external
        # app to authenticate with home assistant so it can reach baby buddy
        session_cookies = {}
        if request.is_homeassistant_ingress_request:
            session_cookies["ingress_session"] = request.COOKIES.get("ingress_session")

        qr_code_response = render(
            request,
            self.qr_code_template,
            {"session_cookies": json.dumps(session_cookies)},
        )
        qr_code_data = qr_code_response.content.decode().strip()

        # Now that the qr_code json-data is assembled, we can pass the json
        # structure as data to the user_add_device - template where it will
        # be converted into a qr-code.
        return render(
            request,
            self.template_name,
            {
                "form_user": self.form_user_class(instance=request.user),
                "qr_code_data": qr_code_data,
            },
        )

    def post(self, request):
        if handle_api_regenerate_request(request):
            return redirect("babybuddy:user-add-device")
        else:
            raise BadRequest()


class Welcome(LoginRequiredMixin, TemplateView):
    """
    Basic introduction to Baby Buddy (meant to be shown when no data is in the
    database).
    """

    template_name = "babybuddy/welcome.html"
