"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: ksp-servicestelle@sgdnord.rlp.de
Created on: 18.08.22

"""
from dal import autocomplete
from django import forms
from django.db import transaction
from django.utils.translation import gettext_lazy as _

from compensation.models import EcoAccount, EcoAccountDeduction
from intervention.models import Intervention
from konova.forms.modals import BaseModalForm, RemoveModalForm
from konova.utils.general import format_german_float
from konova.utils.message_templates import DEDUCTION_ADDED, DEDUCTION_REMOVED, DEDUCTION_EDITED
from user.models import UserActionLogEntry


class NewEcoAccountDeductionModalForm(BaseModalForm):
    """ Form for creating new deduction

    Can be used for Intervention view as well as for EcoAccount views.

    Parameter 'instance' can be an intervention, as well as an ecoAccount.
    An instance check handles both workflows properly.

    """
    account = forms.ModelChoiceField(
        label=_("Eco-account"),
        label_suffix="",
        help_text=_("Only recorded accounts can be selected for deductions"),
        queryset=EcoAccount.objects.filter(deleted=None),
        widget=autocomplete.ModelSelect2(
            url="compensation:acc:autocomplete",
            attrs={
                "data-placeholder": _("Eco-account"),
                "data-minimum-input-length": 3,
                "readonly": True,
            }
        ),
    )
    surface = forms.DecimalField(
        min_value=0.00,
        decimal_places=2,
        label=_("Surface"),
        label_suffix="",
        help_text=_("in m²"),
        widget=forms.NumberInput(
            attrs={
                "class": "form-control",
                "placeholder": "0,00",
            }
        )
    )
    intervention = forms.ModelChoiceField(
        label=_("Intervention"),
        label_suffix="",
        help_text=_("Only shared interventions can be selected"),
        queryset=Intervention.objects.filter(deleted=None),
        widget=autocomplete.ModelSelect2(
            url="intervention:autocomplete",
            attrs={
                "data-placeholder": _("Intervention"),
                "data-minimum-input-length": 3,
            }
        ),
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.form_title = _("New Deduction")
        self.form_caption = _("Enter the information for a new deduction from a chosen eco-account")

        # Check for Intervention or EcoAccount
        if isinstance(self.instance, Intervention):
            # Form has been called with a given intervention
            self.initialize_form_field("intervention", self.instance)
            self.disable_form_field("intervention")
        elif isinstance(self.instance, EcoAccount):
            # Form has been called with a given account --> make it initial in the form and read-only
            self.initialize_form_field("account", self.instance)
            self.disable_form_field("account")
        else:
            raise NotImplementedError

    def _get_available_surface(self, acc):
        """ Calculates how much available surface is left on the account

        Args:
            acc (EcoAccount):

        Returns:

        """
        # Calculate valid surface
        deductable_surface = acc.deductable_surface
        sum_surface_deductions = acc.get_deductions_surface()
        rest_surface = deductable_surface - sum_surface_deductions
        return rest_surface

    def is_valid(self):
        """ Custom validity check

        Makes sure the deduction can not contain more surface than the account still provides

        Returns:
            is_valid (bool)
        """
        super_result = super().is_valid()
        acc = self.cleaned_data["account"]
        intervention = self.cleaned_data["intervention"]
        objects_valid = True

        if not acc.recorded:
            self.add_error(
                "account",
                _("Eco-account {} is not recorded yet. You can only deduct from recorded accounts.").format(acc.identifier)
            )
            objects_valid = False

        if intervention.is_recorded:
            self.add_error(
                "intervention",
                _("Intervention {} is currently recorded. To change any data on it, the entry must be unrecorded.").format(intervention.identifier)
            )
            objects_valid = False

        rest_surface = self._get_available_surface(acc)
        form_surface = float(self.cleaned_data["surface"])
        is_valid_surface = form_surface <= rest_surface
        if not is_valid_surface:
            self.add_error(
                "surface",
                _("The account {} has not enough surface for a deduction of {} m². There are only {} m² left").format(
                    acc.identifier,
                    format_german_float(form_surface),
                    format_german_float(rest_surface),
                ),
            )
        return is_valid_surface and objects_valid and super_result

    def __create_deduction(self):
        """ Creates the deduction

        Returns:

        """
        with transaction.atomic():
            user_action_create = UserActionLogEntry.get_created_action(self.user)
            deduction = EcoAccountDeduction.objects.create(
                intervention=self.cleaned_data["intervention"],
                account=self.cleaned_data["account"],
                surface=self.cleaned_data["surface"],
                created=user_action_create,
            )
        return deduction

    def save(self):
        deduction = self.__create_deduction()
        self.cleaned_data["intervention"].mark_as_edited(self.user, edit_comment=DEDUCTION_ADDED)
        self.cleaned_data["account"].mark_as_edited(self.user, edit_comment=DEDUCTION_ADDED)
        return deduction

    def check_for_recorded_instance(self):
        # Ignore super() implementation
        return


class EditEcoAccountDeductionModalForm(NewEcoAccountDeductionModalForm):
    deduction = None

    def __init__(self, *args, **kwargs):
        self.deduction = kwargs.pop("deduction", None)
        super().__init__(*args, **kwargs)
        self.form_title = _("Edit Deduction")
        form_data = {
            "account": self.deduction.account,
            "intervention": self.deduction.intervention,
            "surface": self.deduction.surface,
        }
        self.load_initial_data(form_data)

    def _get_available_surface(self, acc):
        rest_surface = super()._get_available_surface(acc)
        # Increase available surface by the currently deducted surface, so we can 'deduct' the same amount again or
        # increase the surface only a little, which will still be valid.
        # Example: 200 m² left, 500 m² deducted. Entering 700 m² would fail if we would not add the 500 m² to the available
        # surface again.
        rest_surface += self.deduction.surface
        return rest_surface

    def save(self):
        deduction = self.deduction
        form_account = self.cleaned_data.get("account", None)
        form_intervention = self.cleaned_data.get("intervention", None)
        old_account = deduction.account
        old_intervention = deduction.intervention
        old_surface = deduction.surface

        # If account or intervention has been changed, we put that change in the logs just as if the deduction has
        # been removed for this entry. Act as if the deduction is newly created for the new entries
        if old_account != form_account:
            old_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_REMOVED)
            form_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_ADDED)
        else:
            old_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)

        if old_intervention != form_intervention:
            old_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_REMOVED)
            form_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_ADDED)
        else:
            old_intervention.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)

        deduction.account = form_account
        deduction.intervention = self.cleaned_data.get("intervention", None)
        deduction.surface = self.cleaned_data.get("surface", None)
        deduction.save()

        data_changes = {
            "surface": {
                "old": old_surface,
                "new": deduction.surface,
            },
            "intervention": {
                "old": old_intervention.identifier,
                "new": deduction.intervention.identifier,
            },
            "account": {
                "old": old_account.identifier,
                "new": deduction.account.identifier,
            }
        }
        old_account.send_notification_mail_on_deduction_change(data_changes)
        return deduction

    def check_for_recorded_instance(self):
        """
        Extension to super class base method

        Returns:

        """
        if self.deduction.intervention.is_recorded:
            self.block_form()


class RemoveEcoAccountDeductionModalForm(RemoveModalForm):
    """ Removing modal form for EcoAccountDeduction

    Can be used for anything, where removing shall be confirmed by the user a second time.

    """
    deduction = None

    def __init__(self, *args, **kwargs):
        deduction = kwargs.pop("deduction", None)
        self.deduction = deduction
        super().__init__(*args, **kwargs)

    def save(self):
        with transaction.atomic():
            self.deduction.intervention.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
            self.deduction.account.mark_as_edited(self.user, edit_comment=DEDUCTION_REMOVED)
            self.deduction.delete()

    def check_for_recorded_instance(self):
        if self.deduction.intervention.is_recorded:
            self.block_form()
