#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

from django.forms import ValidationError
from django.urls import reverse
from django.utils.translation import gettext_lazy as _

from horizon import exceptions
from horizon import forms
from horizon import messages

from openstack_dashboard.api import cinder
from openstack_dashboard.api import keystone


class CreateVolumeType(forms.SelfHandlingForm):
    name = forms.CharField(max_length=255, label=_("Name"))
    vol_type_description = forms.CharField(
        max_length=255,
        widget=forms.Textarea(attrs={'rows': 4}),
        label=_("Description"),
        required=False)
    is_public = forms.BooleanField(
        label=_("Public"),
        initial=True,
        required=False,
        help_text=_("By default, volume type is created as public. To "
                    "create a private volume type, uncheck this field."))

    def clean_name(self):
        cleaned_name = self.cleaned_data['name']
        if cleaned_name.isspace():
            raise ValidationError(_('Volume type name can not be empty.'))

        return cleaned_name

    def handle(self, request, data):
        try:
            # Remove any new lines in the public key
            volume_type = cinder.volume_type_create(
                request,
                data['name'],
                data['vol_type_description'],
                data['is_public'])
            messages.success(request, _('Successfully created volume type: %s')
                             % data['name'])
            return volume_type
        except Exception as e:
            if getattr(e, 'code', None) == 409:
                msg = _('Volume type name "%s" already '
                        'exists.') % data['name']
                self._errors['name'] = self.error_class([msg])
            else:
                redirect = reverse("horizon:admin:volume_types:index")
                exceptions.handle(request,
                                  _('Unable to create volume type.'),
                                  redirect=redirect)


class CreateQosSpec(forms.SelfHandlingForm):
    name = forms.CharField(max_length=255, label=_("Name"))
    consumer = forms.ThemableChoiceField(label=_("Consumer"),
                                         choices=cinder.CONSUMER_CHOICES)

    def handle(self, request, data):
        try:
            qos_spec = cinder.qos_spec_create(request,
                                              data['name'],
                                              {'consumer': data['consumer']})
            messages.success(request,
                             _('Successfully created QoS Spec: %s')
                             % data['name'])
            return qos_spec
        except Exception as ex:
            if getattr(ex, 'code', None) == 409:
                msg = _('QoS Spec name "%s" already '
                        'exists.') % data['name']
                self._errors['name'] = self.error_class([msg])
            else:
                redirect = reverse("horizon:admin:volume_types:index")
                exceptions.handle(request,
                                  _('Unable to create QoS Spec.'),
                                  redirect=redirect)


class CreateVolumeTypeEncryption(forms.SelfHandlingForm):
    name = forms.CharField(label=_("Name"), required=False,
                           widget=forms.TextInput(attrs={'readonly':
                                                         'readonly'}))
    provider = forms.CharField(max_length=255, label=_("Provider"))
    control_location = forms.ThemableChoiceField(label=_("Control Location"),
                                                 choices=(('front-end',
                                                           _('front-end')),
                                                          ('back-end',
                                                           _('back-end')))
                                                 )
    cipher = forms.CharField(label=_("Cipher"), required=False)
    key_size = forms.IntegerField(label=_("Key Size (bits)"),
                                  required=False,
                                  min_value=1)
    volume_type_id = forms.CharField(widget=forms.HiddenInput())

    def handle(self, request, data):
        try:
            # Set Cipher to None if empty
            if data['cipher'] == '':
                data['cipher'] = None

            volume_type_id = data.pop('volume_type_id')
            volume_type_name = data.pop('name')

            # Create encryption for the volume type
            volume_type = cinder.\
                volume_encryption_type_create(request,
                                              volume_type_id,
                                              data)
            messages.success(request, _('Successfully created encryption for '
                                        'volume type: %s') % volume_type_name)
            return volume_type
        except Exception:
            redirect = reverse("horizon:admin:volume_types:index")
            exceptions.handle(
                request, _('Unable to create encrypted volume type.'),
                redirect=redirect)


class UpdateVolumeTypeEncryption(CreateVolumeTypeEncryption):

    def handle(self, request, data):
        try:
            # Set Cipher to None if empty
            if data['cipher'] == '':
                data['cipher'] = None

            volume_type_id = data.pop('volume_type_id')
            volume_type_name = data.pop('name')

            # Update encryption for the volume type
            volume_type = cinder.\
                volume_encryption_type_update(request,
                                              volume_type_id,
                                              data)
            messages.success(request, _('Successfully updated encryption for '
                                        'volume type: %s') % volume_type_name)
            return volume_type
        except NotImplementedError:
            messages.error(request, _('Updating encryption is not '
                                      'implemented.  Unable to update '
                                      ' encrypted volume type.'))
        except Exception:
            redirect = reverse("horizon:admin:volume_types:index")
            exceptions.handle(request,
                              _('Unable to update encrypted volume type.'),
                              redirect=redirect)
        return False


class ManageQosSpecAssociation(forms.SelfHandlingForm):
    qos_spec_choice = forms.ThemableChoiceField(
        label=_("QoS Spec to be associated"),
        help_text=_("Choose associated QoS Spec."))

    def __init__(self, request, *args, **kwargs):
        super().__init__(request, *args, **kwargs)
        qos_spec_field = self.fields['qos_spec_choice']
        qos_spec_field.choices = \
            self.populate_qos_spec_choices()

    def populate_qos_spec_choices(self):
        # populate qos spec list box
        qos_specs = self.initial["qos_specs"]
        current_qos_spec = self.initial["cur_qos_spec_id"]
        qos_spec_list = [(qos_spec.id, qos_spec.name)
                         for qos_spec in qos_specs
                         if qos_spec.id != current_qos_spec]

        if current_qos_spec:
            # used to remove the current spec
            qos_spec_list.insert(0, ("-1", _("None (removes spec)")))
        if qos_spec_list:
            qos_spec_list.insert(0, ("", _("Select a new QoS spec")))
        else:
            qos_spec_list.insert(0, ("", _("No new QoS spec available")))
        return qos_spec_list

    def handle(self, request, data):
        vol_type_id = self.initial['type_id']
        new_qos_spec_id = data['qos_spec_choice']

        # Update QOS Spec association information
        try:
            # NOTE - volume types can only be associated with
            #        ONE QOS Spec at a time

            # first we need to un-associate the current QOS Spec, if it exists
            cur_qos_spec_id = self.initial['cur_qos_spec_id']
            if cur_qos_spec_id:
                qos_spec = cinder.qos_spec_get(request,
                                               cur_qos_spec_id)
                cinder.qos_spec_disassociate(request,
                                             qos_spec,
                                             vol_type_id)

            # now associate with new QOS Spec, if user wants one associated
            if new_qos_spec_id != '-1':
                qos_spec = cinder.qos_spec_get(request,
                                               new_qos_spec_id)

                cinder.qos_spec_associate(request,
                                          qos_spec,
                                          vol_type_id)

            messages.success(request,
                             _('Successfully updated QoS Spec association.'))
            return True
        except Exception:
            redirect = reverse("horizon:admin:volume_types:index")
            exceptions.handle(request,
                              _('Error updating QoS Spec association.'),
                              redirect=redirect)


class EditQosSpecConsumer(forms.SelfHandlingForm):
    current_consumer = forms.CharField(label=_("Current consumer"),
                                       widget=forms.TextInput(
                                       attrs={'readonly': 'readonly'}),
                                       required=False)
    consumer_choice = forms.ThemableChoiceField(
        label=_("New QoS Spec Consumer"),
        choices=cinder.CONSUMER_CHOICES,
        help_text=_("Choose consumer for this QoS Spec."))

    def __init__(self, request, *args, **kwargs):
        super().__init__(request, *args, **kwargs)
        consumer_field = self.fields['consumer_choice']
        qos_spec = self.initial["qos_spec"]
        self.fields['current_consumer'].initial = qos_spec.consumer
        choices = [choice for choice in cinder.CONSUMER_CHOICES
                   if choice[0] != qos_spec.consumer]
        choices.insert(0, ("", _("Select a new consumer")))
        consumer_field.choices = choices

    def handle(self, request, data):
        qos_spec_id = self.initial['qos_spec_id']
        new_consumer = data['consumer_choice']

        # Update QOS Spec consumer information
        try:
            cinder.qos_spec_set_keys(request,
                                     qos_spec_id,
                                     {'consumer': new_consumer})
            messages.success(request,
                             _('Successfully modified QoS Spec consumer.'))
            return True
        except Exception:
            redirect = reverse("horizon:admin:volume_types:index")
            exceptions.handle(request, _('Error editing QoS Spec consumer.'),
                              redirect=redirect)


class EditVolumeType(forms.SelfHandlingForm):
    name = forms.CharField(max_length=255,
                           label=_("Name"))
    description = forms.CharField(max_length=255,
                                  widget=forms.Textarea(attrs={'rows': 4}),
                                  label=_("Description"),
                                  required=False)
    is_public = forms.BooleanField(label=_("Public"), required=False,
                                   help_text=_(
                                       "To make volume type private, uncheck "
                                       "this field."))

    def clean_name(self):
        cleaned_name = self.cleaned_data['name']
        if cleaned_name.isspace():
            msg = _('New name cannot be empty.')
            self._errors['name'] = self.error_class([msg])

        return cleaned_name

    def handle(self, request, data):
        volume_type_id = self.initial['id']
        try:
            cinder.volume_type_update(request,
                                      volume_type_id,
                                      data['name'],
                                      data['description'],
                                      data['is_public'])
            message = _('Successfully updated volume type.')
            messages.success(request, message)
            return True
        except Exception as ex:
            redirect = reverse("horizon:admin:volume_types:index")
            if ex.code == 409:
                error_message = _('New name conflicts with another '
                                  'volume type.')
            else:
                error_message = _('Unable to update volume type.')

            exceptions.handle(request, error_message,
                              redirect=redirect)


class EditTypeAccessForm(forms.SelfHandlingForm):

    def __init__(self, request, *args, **kwargs):
        super().__init__(request, *args, **kwargs)
        err_msg = _('Unable to retrieve volume type access list.')

        self.fields["member"] = forms.MultipleChoiceField(
            required=False,
            widget=forms.ThemableCheckboxSelectMultiple())
        # Get list of available projects.
        try:
            all_projects, has_more = keystone.tenant_list(request)
        except Exception:
            exceptions.handle(request, err_msg)
        projects_list = [(project.id, project.name)
                         for project in all_projects]

        self.fields["member"].choices = projects_list

        volume_type_id = self.initial.get('volume_type_id')
        volume_type_access = []
        try:
            if volume_type_id:
                volume_type = cinder.volume_type_get(request,
                                                     volume_type_id)
                if not volume_type.is_public:
                    volume_type_access = [
                        project.project_id for project in
                        cinder.volume_type_access_list(request,
                                                       volume_type_id)]
        except Exception:
            exceptions.handle(request, err_msg)

        self.fields["member"].initial = volume_type_access

    def handle(self, request, data):
        type_id = self.initial['volume_type_id']
        current_projects = self.fields["member"].initial

        removed_projects = current_projects
        for p in data['member']:
            if p not in current_projects:
                # Newly added project access
                try:
                    cinder.volume_type_add_project_access(request, type_id, p)
                except Exception:
                    exceptions.handle(request,
                                      _('Failed to add project %(project)s to '
                                        'volume type access.') %
                                      {'project': p})
            else:
                removed_projects.remove(p)
        for p in removed_projects:
            try:
                cinder.volume_type_remove_project_access(request, type_id, p)
            except Exception:
                exceptions.handle(request, _('Failed to remove project '
                                             '%(project)s from volume type '
                                             'access.') % {'project': p})
        messages.success(request,
                         _('Modified volume type access: %s') % type_id)
        return True
