# barbacompta - invoicing for dummies
# Copyright (C) 2019-2020 Entr'ouvert
#
# 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, either version 3 of the License, or
# (at your option) any later version.
#
# 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 <http://www.gnu.org/licenses/>.


from decimal import Decimal, InvalidOperation

from django import forms
from django.core import validators
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.six import text_type
from django.utils.translation import ugettext_lazy as _


class PercentagePerYear(list):
    def __str__(self):
        return ','.join(map(lambda p: ':'.join(map(str, [p[0], int(100 * p[1])])), self))


class EuroField(models.DecimalField):
    def __init__(self, *args, **kwargs):
        kwargs["max_digits"] = 8
        kwargs["decimal_places"] = 2
        super().__init__(*args, **kwargs)


def assertion_error_to_validation_error(fun):
    def f(*args, **kwargs):
        try:
            return fun(*args, **kwargs)
        except (AssertionError, ValueError) as e:
            raise ValidationError(*e.args)

    return f


@assertion_error_to_validation_error
def check_percentage_per_year(value):
    years = [a for a, b in value]
    percentages = [b for a, b in value]
    # ordered
    assert years == sorted(years), "years are not ordered"
    # sum equals 100
    assert sum(percentages) == 1, "percentage does not sum to 100"
    # no duplicate year
    assert len(years) == len(set(years)), "years are not unique"
    # consecutive
    assert years == list(range(years[0], years[0] + len(years))), 'years are not consecutive'


def parse_percentage_per_year(value):
    msg = _("field must be numeric values separated by commas")
    values = value.split(",")
    decimals = []
    for value in values:
        try:
            year, decimal = value.split(":")
        except ValueError:
            raise ValidationError(msg)
        try:
            year = int(year.strip())
        except ValueError:
            raise ValidationError(msg)
        try:
            decimal = Decimal(decimal.strip()) / 100
        except InvalidOperation:
            raise ValidationError(msg)
        decimals.append((year, decimal))
    return PercentagePerYear(decimals)


class PercentagePerYearFormField(forms.Field):
    default_error_messages = {
        'invalid': _('field must be numeric values separated by commas'),
    }

    def to_python(self, value):
        if value in validators.EMPTY_VALUES:
            return None
        if isinstance(PercentagePerYear, value):
            return value
        if not isinstance(text_type, value):
            raise ValidationError(self.default_error_messages["invalid"])
        return parse_percentage_per_year(value)


class PercentagePerYearField(models.Field):
    default_validators = [check_percentage_per_year]

    def __init__(self, *args, **kwargs):
        kwargs["max_length"] = 64
        super().__init__(*args, **kwargs)

    def from_db_value(self, value, expression, connection):
        return self.to_python(value)

    def to_python(self, value):
        if isinstance(value, PercentagePerYear):
            return value
        if value is not None:
            percentage_per_year = []
            pairs = value.split(",")
            for pair in pairs:
                try:
                    year, percentage = map(value.__class__.strip, pair.split(":"))
                except ValueError:
                    raise ValidationError(PercentagePerYearFormField.default_error_messages["invalid"])
                year = int(year)
                percentage = Decimal(percentage) / Decimal(100)
                percentage_per_year.append((year, percentage))
            try:
                return PercentagePerYear(percentage_per_year)
            except forms.ValidationError:
                return value
        return value

    def get_prep_value(self, value):
        if value is not None:
            if isinstance(value, str):
                value = parse_percentage_per_year(value)
            else:
                value = PercentagePerYear(value)
            value = str(value)
        return super().get_prep_value(value)

    def get_internal_type(self):
        return "CharField"

    def formfield(self, **kwargs):
        defaults = {"form_class": PercentagePerYearFormField}
        defaults.update(kwargs)
        return super().formfield(**kwargs)
