# SPDX-License-Identifier: AGPL-3.0-or-later
import io
import itertools
import pwd

from django.core.exceptions import ValidationError
from django.http import HttpResponseBadRequest
from django.shortcuts import redirect
from django.utils.html import escape
from django.utils.translation import gettext_lazy as _
from django.views.generic.base import TemplateView, View

import plinth.actions
import plinth.utils
from plinth.views import AppView, render_tabs

from . import aliases, audit, forms


class TabMixin(View):
    admin_tabs = [
        ('', _('Home')),
        ('my_mail', _('My Mail')),
        ('my_aliases', _('My Aliases')),
        ('security', _('Security')),
        ('domains', _('Domains'))
    ]

    user_tabs = [
        ('my_mail', _('Home')),
        ('my_aliases', _('My Aliases'))
    ]

    def get_context_data(self, *args, **kwargs):
        # Retrieve context data from the next method in the MRO
        context = super().get_context_data(*args, **kwargs)
        # Populate context with customized data
        context['tabs'] = self.render_dynamic_tabs()
        return context

    def render_dynamic_tabs(self):
        if plinth.utils.is_user_admin(self.request):
            return render_tabs(self.request.path, self.admin_tabs)
        else:
            return render_tabs(self.request.path, self.user_tabs)

    def render_validation_error(self, validation_error, status=400):
        context = self.get_context_data()
        context['error'] = validation_error
        return self.render_to_response(context, status=status)

    def render_exception(self, exception, status=500):
        context = self.get_context_data()
        context['error'] = [str(exception)]
        return self.render_to_response(context, status=status)

    def catch_exceptions(self, function, request):
        try:
            return function(request)
        except ValidationError as validation_error:
            return self.render_validation_error(validation_error)
        except Exception as error:
            return self.render_exception(error)

    def find_button(self, post):
        key_filter = (k for k in post.keys() if k.startswith('btn_'))
        lst = list(itertools.islice(key_filter, 2))
        if len(lst) != 1:
            raise ValidationError('Bad post data')
        if not isinstance(lst[0], str):
            raise ValidationError('Bad post data')
        return lst[0][len('btn_'):]

    def find_form(self, post):
        form_name = post.get('form')
        for cls in self.form_classes:
            if cls.__name__ == form_name:
                return cls(post)
        raise ValidationError('Form was unspecified')


class EmailServerView(TabMixin, AppView):
    """Server configuration page"""
    app_id = 'email_server'
    template_name = 'email_server.html'
    audit_modules = ('domain', 'tls', 'rcube')

    def get_context_data(self, *args, **kwargs):
        dlist = []
        for module_name in self.audit_modules:
            self._get_audit_results(module_name, dlist)
        dlist.sort(key=audit.models.Diagnosis.sorting_key)

        context = super().get_context_data(*args, **kwargs)
        context['related_diagnostics'] = dlist
        return context

    def _get_audit_results(self, module_name, dlist):
        try:
            results = getattr(audit, module_name).get()
        except Exception as e:
            title = _('Internal error in {0}').format('audit.' + module_name)
            diagnosis = audit.models.Diagnosis(title)
            diagnosis.critical(str(e))
            diagnosis.critical(_('Check syslog for more information'))
            results = [diagnosis]

        for diagnosis in results:
            if diagnosis.action:
                diagnosis.action = '%s.%s' % (module_name, diagnosis.action)
            if diagnosis.has_failed:
                dlist.append(diagnosis)

    def post(self, request):
        repair_field = request.POST.get('repair')
        module_name, sep, action_name = repair_field.partition('.')
        if not sep or module_name not in self.audit_modules:
            return HttpResponseBadRequest('Bad post data')

        self._repair(module_name, action_name)
        return redirect(request.path)

    def _repair(self, module_name, action_name):
        module = getattr(audit, module_name)
        if not hasattr(module, 'repair_component'):
            return

        reload_list = []
        try:
            reload_list = module.repair_component(action_name)
        except Exception:
            pass

        for service in reload_list:
            # plinth.action_utils.service_reload(service)
            plinth.actions.superuser_run('service', ['reload', service])


class MyMailView(TabMixin, TemplateView):
    template_name = 'my_mail.html'

    def get_context_data(self, *args, **kwargs):
        context = super().get_context_data(*args, **kwargs)

        nam = self.request.user.username
        context['has_homedir'] = audit.home.exists_nam(nam)

        return context

    def post(self, request):
        return self.catch_exceptions(self._post, request)

    def _post(self, request):
        if 'btn_mkhome' not in request.POST:
            raise ValidationError('Bad post data')
        audit.home.put_nam(request.user.username)
        return self.render_to_response(self.get_context_data())


class AliasView(TabMixin, TemplateView):
    class Checkboxes:
        def __init__(self, post=None, initial=None):
            self.models = initial
            self.post = post
            self.cleaned_data = {}
            # HTML rendering
            self.sb = io.StringIO()
            self.counter = 0

        def render(self):
            if self.models is None:
                raise RuntimeError('Uninitialized form')
            if self.sb.tell() > 0:
                raise RuntimeError('render has been called')

            enabled = [a.email_name for a in self.models if a.enabled]
            disabled = [a.email_name for a in self.models if not a.enabled]

            self._render_fieldset(enabled, _('Enabled aliases'))
            self._render_fieldset(disabled, _('Disabled aliases'))

            return self.sb.getvalue()

        def _render_fieldset(self, email_names, legend):
            if len(email_names) > 0:
                self.sb.write('<fieldset class="form-group">')
                self.sb.write('<legend>%s</legend>' % escape(legend))
                self._render_boxes(email_names)
                self.sb.write('</fieldset>')

        def _render_boxes(self, email_names):
            for email_name in email_names:
                input_id = 'cb_alias_%d' % self._count()
                value = escape(email_name)
                self.sb.write('<div class="form-check">')

                self.sb.write('<input type="checkbox" name="alias" ')
                self.sb.write('class="form-check-input" ')
                self.sb.write('id="%s" value="%s">' % (input_id, value))

                self.sb.write('<label class="form-check-label" ')
                self.sb.write('for="%s">%s</label>' % (input_id, value))

                self.sb.write('</div>')

        def _count(self):
            self.counter += 1
            return self.counter

        def is_valid(self):
            lst = list(filter(None, self.post.getlist('alias')))
            if not lst:
                return False
            else:
                self.cleaned_data['alias'] = lst
                return True

    template_name = 'email_alias.html'
    form_classes = (forms.AliasCreationForm, Checkboxes)

    def get_context_data(self, *args, **kwargs):
        context = super().get_context_data(*args, **kwargs)
        context['form'] = forms.AliasCreationForm()

        uid = pwd.getpwnam(self.request.user.username).pw_uid
        models = aliases.get(uid)
        if len(models) > 0:
            form = AliasView.Checkboxes(initial=models)
            context['alias_boxes'] = form.render()
        else:
            context['no_alias'] = True
        return context

    def post(self, request):
        return self.catch_exceptions(self._post, request)

    def _post(self, request):
        form = self.find_form(request.POST)
        button = self.find_button(request.POST)
        if not form.is_valid():
            raise ValidationError('Form invalid')

        if isinstance(form, AliasView.Checkboxes):
            if button not in ('delete', 'disable', 'enable'):
                raise ValidationError('Bad button')
            return self.alias_operation_form_valid(form, button)

        if isinstance(form, forms.AliasCreationForm):
            if button != 'add':
                raise ValidationError('Bad button')
            return self.alias_creation_form_valid(form, button)

        raise RuntimeError('Unknown form')

    def alias_operation_form_valid(self, form, button):
        uid = pwd.getpwnam(self.request.user.username).pw_uid
        alias_list = form.cleaned_data['alias']
        if button == 'delete':
            aliases.delete(uid, alias_list)
        elif button == 'disable':
            aliases.set_disabled(uid, alias_list)
        elif button == 'enable':
            aliases.set_enabled(uid, alias_list)
        return self.render_to_response(self.get_context_data())

    def alias_creation_form_valid(self, form, button):
        uid = pwd.getpwnam(self.request.user.username).pw_uid
        aliases.put(uid, form.cleaned_data['email_name'])
        return self.render_to_response(self.get_context_data())


class TLSView(TabMixin, TemplateView):
    template_name = 'email_security.html'


class DomainView(TabMixin, TemplateView):
    template_name = 'email_domains.html'

    def get_context_data(self, *args, **kwargs):
        context = super().get_context_data(*args, **kwargs)
        fields = audit.domain.get_domain_config()
        # If having post data, display the posted values
        for field in fields:
            field.new_value = self.request.POST.get(field.key, '')
        context['fields'] = fields
        return context

    def post(self, request):
        return self.catch_exceptions(self._post, request)

    def _post(self, request):
        changed = {}
        # Skip blank fields
        for key, value in request.POST.items():
            value = value.strip()
            if value:
                changed[key] = value
        audit.domain.set_keys(changed)
        return self.render_to_response(self.get_context_data())


class XmlView(TemplateView):
    template_name = 'email_autoconfig.xml'

    def render_to_response(self, *args, **kwargs):
        kwargs['content_type'] = 'text/xml; charset=utf-8'
        response = super().render_to_response(*args, **kwargs)
        response['X-Robots-Tag'] = 'noindex, nofollow, noarchive'
        return response

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['host'] = self.request.get_host()
        return context
