#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program.  If not, see
# <https://www.gnu.org/licenses/>.
#

# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of
# the Apache License 2.0 can be obtained at <http://www.apache.org/licenses/LICENSE-2.0>.
#
# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A
# full history of changes and contributors is available at <https://github.com/pretix/pretix>.
#
# This file contains Apache-licensed contributions copyrighted by: Jan-Frederik Rieckers, Sohalt, Tobias Kunze
#
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 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 itertools import chain

from django import forms
from django.core.exceptions import ValidationError
from django.utils.encoding import force_str
from django.utils.formats import date_format
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from phonenumber_field.formfields import PhoneNumberField

from pretix.base.forms.questions import (
    BaseInvoiceAddressForm, BaseQuestionsForm, WrappedPhoneNumberPrefixWidget,
    guess_phone_prefix_from_request,
)
from pretix.base.templatetags.rich_text import rich_text
from pretix.base.validators import EmailBanlistValidator
from pretix.presale.signals import contact_form_fields


class ContactForm(forms.Form):
    required_css_class = 'required'
    email = forms.EmailField(label=_('E-mail'),
                             validators=[EmailBanlistValidator()],
                             widget=forms.EmailInput(attrs={'autocomplete': 'section-contact email'})
                             )

    def __init__(self, *args, **kwargs):
        self.event = kwargs.pop('event')
        self.request = kwargs.pop('request')
        self.all_optional = kwargs.pop('all_optional', False)
        super().__init__(*args, **kwargs)

        if self.event.settings.order_email_asked_twice:
            self.fields['email_repeat'] = forms.EmailField(
                label=_('E-mail address (repeated)'),
                help_text=_('Please enter the same email address again to make sure you typed it correctly.'),
            )

        if self.event.settings.order_phone_asked:
            if not self.initial.get('phone'):
                phone_prefix = guess_phone_prefix_from_request(self.request, self.event)
                if phone_prefix:
                    # We now exploit an implementation detail in PhoneNumberPrefixWidget to allow us to pass just
                    # a country code but no number as an initial value. It's a bit hacky, but should be stable for
                    # the future.
                    self.initial['phone'] = "+{}.".format(phone_prefix)

            self.fields['phone'] = PhoneNumberField(
                label=_('Phone number'),
                required=self.event.settings.order_phone_required,
                help_text=rich_text(self.event.settings.checkout_phone_helptext),
                widget=WrappedPhoneNumberPrefixWidget()
            )

        if not self.request.session.get('iframe_session', False):
            # There is a browser quirk in Chrome that leads to incorrect initial scrolling in iframes if there
            # is an autofocus field. Who would have thought… See e.g. here:
            # https://floatboxjs.com/forum/topic.php?post=8440&usebb_sid=2e116486a9ec6b7070e045aea8cded5b#post8440
            self.fields['email'].widget.attrs['autofocus'] = 'autofocus'
        self.fields['email'].help_text = rich_text(self.event.settings.checkout_email_helptext)

        responses = contact_form_fields.send(self.event, request=self.request)
        for r, response in responses:
            for key, value in response.items():
                # We need to be this explicit, since OrderedDict.update does not retain ordering
                self.fields[key] = value
        if self.all_optional:
            for k, v in self.fields.items():
                v.required = False
                v.widget.is_required = False

    def clean(self):
        if self.event.settings.order_email_asked_twice and self.cleaned_data.get('email') and self.cleaned_data.get('email_repeat'):
            if self.cleaned_data.get('email').lower() != self.cleaned_data.get('email_repeat').lower():
                raise ValidationError(_('Please enter the same email address twice.'))


class InvoiceAddressForm(BaseInvoiceAddressForm):
    required_css_class = 'required'
    vat_warning = True
    address_validation = True

    def __init__(self, *args, **kwargs):
        allow_save = kwargs.pop('allow_save', False)
        super().__init__(*args, **kwargs)
        if allow_save:
            self.fields['saved_id'] = forms.IntegerField(
                required=False,
                help_text=" ",  # non-breaking-space, will be overwritten by JavaScript, needed here for HTML-output
                label=_("Save to address"),
                widget=forms.Select(choices=(("", _("Create new address")),))
            )
            self.fields['save'] = forms.BooleanField(
                label=_('Save address in my customer account for future purchases'),
                required=False,
                initial=False,
            )


class InvoiceNameForm(InvoiceAddressForm):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for f in list(self.fields.keys()):
            if f != 'name_parts':
                del self.fields[f]


class QuestionsForm(BaseQuestionsForm):
    """
    This form class is responsible for asking order-related questions. This includes
    the attendee name for admission tickets, if the corresponding setting is enabled,
    as well as additional questions defined by the organizer.
    """
    required_css_class = 'required'
    address_validation = True

    def __init__(self, *args, **kwargs):
        allow_save = kwargs.pop('allow_save', False)
        super().__init__(*args, **kwargs)
        if allow_save and self.fields:
            self.fields['save'] = forms.BooleanField(
                label=_('Save answers to my customer profiles for future purchases'),
                required=False,
                initial=False,
            )
            self.fields['saved_id'] = forms.IntegerField(
                required=False,
                help_text=" ",  # non-breaking-space, will be overwritten by JavaScript, needed here for HTML-output
                label=_("Save to profile"),
                widget=forms.Select(choices=(("", _("Create new profile")),))
            )


class AddOnRadioSelect(forms.RadioSelect):
    option_template_name = 'pretixpresale/forms/addon_choice_option.html'

    def optgroups(self, name, value, attrs=None):
        attrs = attrs or {}
        groups = []
        has_selected = False
        for index, (option_value, option_label, option_desc) in enumerate(chain(self.choices)):
            if option_value is None:
                option_value = ''
            if isinstance(option_label, (list, tuple)):
                raise TypeError('Choice groups are not supported here')
            group_name = None
            subgroup = []
            groups.append((group_name, subgroup, index))

            selected = (
                force_str(option_value) in value and
                (has_selected is False or self.allow_multiple_selected)
            )
            if selected is True and has_selected is False:
                has_selected = True
            attrs['description'] = option_desc
            subgroup.append(self.create_option(
                name, option_value, option_label, selected, index,
                subindex=None, attrs=attrs,
            ))

        return groups


class AddOnVariationField(forms.ChoiceField):
    def valid_value(self, value):
        text_value = force_str(value)
        for k, v, d in self.choices:
            if value == k or text_value == force_str(k):
                return True
        return False


class MembershipForm(forms.Form):
    required_css_class = 'required'

    def __init__(self, *args, **kwargs):
        self.memberships = kwargs.pop('memberships')
        event = kwargs.pop('event')
        self.position = kwargs.pop('position')

        super().__init__(*args, **kwargs)

        ev = self.position.subevent or event
        if self.position.variation and self.position.variation.require_membership:
            types = self.position.variation.require_membership_types.all()
        else:
            types = self.position.item.require_membership_types.all()

        memberships = [
            m for m in self.memberships
            if m.membership_type in types and (
                m.is_valid(ev, self.position.valid_from, valid_from_not_chosen=self.position.item.validity_dynamic_start_choice)
            )
        ]

        if len(memberships) == 1:
            self.initial['membership'] = str(memberships[0].pk)

        self.fields['membership'] = forms.ChoiceField(
            label=_('Membership'),
            choices=[
                (str(m.pk), self._label_from_instance(m))
                for m in memberships
            ],
            widget=forms.RadioSelect,
        )
        self.is_empty = not memberships

    def _label_from_instance(self, obj):
        ds = date_format(obj.date_start, 'SHORT_DATE_FORMAT')
        de = date_format(obj.date_end, 'SHORT_DATE_FORMAT')
        if obj.membership_type.max_usages is not None:
            usages = f'({obj.usages} / {obj.membership_type.max_usages})'
        else:
            usages = ''
        d = f'<strong>{escape(obj.membership_type)}</strong> {usages}<br>'
        if obj.attendee_name:
            d += f'{escape(obj.attendee_name)}<br>'
        d += f'<span class="text-muted">{ds} – {de}</span>'
        if obj.testmode:
            d += ' <span class="label label-warning">{}</span>'.format(_("TEST MODE"))
        return mark_safe(d)

    def clean(self):
        d = super().clean()
        if d.get('membership'):
            d['membership'] = [m for m in self.memberships if str(m.pk) == d['membership']][0]
        return d
