"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 27.09.21

"""
from dal import autocomplete
from django.core.exceptions import ObjectDoesNotExist

from konova.utils.message_templates import DEDUCTION_ADDED, REVOCATION_ADDED, DEDUCTION_REMOVED, DEDUCTION_EDITED, \
    REVOCATION_EDITED, ENTRY_REMOVE_MISSING_PERMISSION
from user.models import User, Team
from user.models import UserActionLogEntry
from django.db import transaction
from django import forms
from django.utils.translation import gettext_lazy as _

from compensation.models import EcoAccount, EcoAccountDeduction
from intervention.inputs import TextToClipboardInput
from intervention.models import Intervention, InterventionDocument, RevocationDocument
from konova.forms import BaseModalForm, NewDocumentModalForm, RemoveModalForm
from konova.utils.general import format_german_float
from konova.utils.user_checks import is_default_group_only


class ShareModalForm(BaseModalForm):
    url = forms.CharField(
        label=_("Share link"),
        label_suffix="",
        help_text=_("Send this link to users who you want to have writing access on the data"),
        required=False,
        widget=TextToClipboardInput(
            attrs={
                "readonly": True,
                "class": "form-control",
            }
        )
    )
    teams = forms.ModelMultipleChoiceField(
        label=_("Add team to share with"),
        label_suffix="",
        help_text=_("Multiple selection possible - You can only select teams which do not already have access."),
        required=False,
        queryset=Team.objects.all(),
        widget=autocomplete.ModelSelect2Multiple(
            url="share-team-autocomplete",
            attrs={
                "data-placeholder": _("Click for selection"),
                "data-minimum-input-length": 3,
            },
        ),
    )
    users = forms.ModelMultipleChoiceField(
        label=_("Add user to share with"),
        label_suffix="",
        help_text=_("Multiple selection possible - You can only select users which do not already have access. Enter the full username."),
        required=False,
        queryset=User.objects.all(),
        widget=autocomplete.ModelSelect2Multiple(
            url="share-user-autocomplete",
            attrs={
                "data-placeholder": _("Click for selection"),
                "data-minimum-input-length": 3,
            },
        ),
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.form_title = _("Share")
        self.form_caption = _("Share settings for {}").format(self.instance.identifier)
        self.template = "modal/modal_form.html"

        # Make sure an access_token is set
        if self.instance.access_token is None:
            self.instance.generate_access_token()

        self._init_fields()

    def _user_team_valid(self):
        """ Checks whether users and teams have been removed by the user and if the user is allowed to do so or not

        Returns:

        """
        users = self.cleaned_data.get("users", User.objects.none())
        teams = self.cleaned_data.get("teams", Team.objects.none())

        _is_valid = True
        if is_default_group_only(self.user):
            shared_users = self.instance.shared_users
            shared_teams = self.instance.shared_teams

            shared_users_are_removed = not set(shared_users).issubset(users)
            shared_teams_are_removed = not set(shared_teams).issubset(teams)

            if shared_users_are_removed:
                self.add_error(
                    "users",
                    ENTRY_REMOVE_MISSING_PERMISSION
                )
                _is_valid = False
            if shared_teams_are_removed:
                self.add_error(
                    "teams",
                    ENTRY_REMOVE_MISSING_PERMISSION
                )
                _is_valid = False
        return _is_valid

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

        Returns:

        """
        super_valid = super().is_valid()
        user_team_valid = self._user_team_valid()
        _is_valid = super_valid and user_team_valid
        return _is_valid

    def _init_fields(self):
        """ Wraps initializing of fields

        Returns:

        """
        # Initialize share_link field
        share_link = self.instance.get_share_link()
        self.share_link = self.request.build_absolute_uri(share_link)
        self.initialize_form_field(
            "url",
            self.share_link
        )

        form_data = {
            "teams": self.instance.teams.all(),
            "users": self.instance.users.all(),
        }
        self.load_initial_data(form_data)

    def save(self):
        self.instance.update_shared_access(self)


class NewRevocationModalForm(BaseModalForm):
    date = forms.DateField(
        label=_("Date"),
        label_suffix=_(""),
        help_text=_("Date of revocation"),
        widget=forms.DateInput(
            attrs={
                "type": "date",
                "data-provide": "datepicker",
                "class": "form-control",
            },
            format="%d.%m.%Y"
        )
    )
    file = forms.FileField(
        label=_("Document"),
        label_suffix=_(""),
        required=False,
        help_text=_("Must be smaller than 15 Mb"),
        widget=forms.FileInput(
            attrs={
                "class": "form-control-file"
            }
        )
    )
    comment = forms.CharField(
        required=False,
        max_length=200,
        label=_("Comment"),
        label_suffix=_(""),
        help_text=_("Additional comment, maximum {} letters").format(200),
        widget=forms.Textarea(
            attrs={
                "cols": 30,
                "rows": 5,
                "class": "form-control",
            }
        )
    )
    document_model = RevocationDocument

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.form_title = _("Add revocation")
        self.form_caption = ""
        self.form_attrs = {
            "enctype": "multipart/form-data",  # important for file upload
        }

    def save(self):
        revocation = self.instance.add_revocation(self)
        self.instance.mark_as_edited(self.user, self.request, edit_comment=REVOCATION_ADDED)
        return revocation


class EditRevocationModalForm(NewRevocationModalForm):
    revocation = None

    def __init__(self, *args, **kwargs):
        self.revocation = kwargs.pop("revocation", None)
        super().__init__(*args, **kwargs)
        try:
            doc = self.revocation.document.file
        except ObjectDoesNotExist:
            doc = None
        form_data = {
            "date": str(self.revocation.date),
            "file": doc,
            "comment": self.revocation.comment,
        }
        self.load_initial_data(form_data)

    def save(self):
        revocation = self.instance.edit_revocation(self)
        self.instance.mark_as_edited(self.user, self.request, edit_comment=REVOCATION_EDITED)
        return revocation


class RemoveRevocationModalForm(RemoveModalForm):
    """ Removing modal form for Revocation

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

    """
    revocation = None

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

    def save(self):
        self.instance.remove_revocation(self)


class CheckModalForm(BaseModalForm):
    """ The modal form for running a check on interventions and their compensations

    """
    checked_intervention = forms.BooleanField(
        label=_("Checked intervention data"),
        label_suffix="",
        widget=forms.CheckboxInput(),
        required=True,
    )
    checked_comps = forms.BooleanField(
        label=_("Checked compensations data and payments"),
        label_suffix="",
        widget=forms.CheckboxInput(),
        required=True
    )
    valid = None

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.form_title = _("Run check")
        self.form_caption = _("I, {} {}, confirm that all necessary control steps have been performed by myself.").format(self.user.first_name, self.user.last_name)
        self.valid = False

    def _are_deductions_valid(self):
        """ Performs validity checks on deductions and their eco-account

        Returns:

        """
        deductions = self.instance.deductions.all()
        for deduction in deductions:
            checker = deduction.account.quality_check()
            for msg in checker.messages:
                self.add_error(
                    "checked_comps",
                    f"{deduction.account.identifier}: {msg}"
                )
            return checker.valid
        return True

    def _are_comps_valid(self):
        """ Performs validity checks on all types of compensations

        Types of compensations are
            * regular Compensations
            * deductions from EcoAccounts

        Returns:

        """
        comps = self.instance.compensations.filter(
            deleted=None,
        )
        comps_valid = True
        for comp in comps:
            checker = comp.quality_check()
            for msg in checker.messages:
                self.add_error(
                    "checked_comps",
                    f"{comp.identifier}: {msg}"
                )
            comps_valid = checker.valid
        deductions_valid = self._are_deductions_valid()
        return deductions_valid and comps_valid

    def is_valid(self) -> bool:
        """ Perform a validity check based on quality_check() logic

        Returns:
            result (bool)
        """
        super_valid = super().is_valid()
        # Perform check
        checker = self.instance.quality_check()
        for msg in checker.messages:
            self.add_error(
                "checked_intervention",
                msg
            )
        all_comps_valid = self._are_comps_valid()
        intervention_valid = checker.valid

        return super_valid and intervention_valid and all_comps_valid

    def save(self):
        """ Saving logic

        Returns:

        """
        with transaction.atomic():
            self.instance.set_checked(self.user)


class NewDeductionModalForm(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="accounts-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="interventions-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"]

        if not acc.recorded:
            self.add_error(
                "account",
                _("Eco-account {} is not recorded yet. You can only deduct from recorded accounts.").format(acc.identifier)
            )
            return 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 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


class EditEcoAccountDeductionModalForm(NewDeductionModalForm):
    deduction = None

    def __init__(self, *args, **kwargs):
        self.deduction = kwargs.pop("deduction", None)
        super().__init__(*args, **kwargs)
        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)
        current_account = deduction.account
        current_intervention = deduction.intervention


        # 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 current_account != form_account:
            current_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:
            current_account.mark_as_edited(self.user, self.request, edit_comment=DEDUCTION_EDITED)

        if current_intervention != form_intervention:
            current_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:
            current_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()
        return deduction


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()


class NewInterventionDocumentModalForm(NewDocumentModalForm):
    document_model = InterventionDocument