#  -*- coding: utf-8 -*-
#  Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import models
from odoo.tools import float_compare, float_is_zero, float_round


class AccountAnalyticPlan(models.Model):
    _inherit = 'account.analytic.plan'

    def _calculate_distribution_amount(self, amount, percentage, total_percentage, distribution_on_each_plan):
        """
        Ensures that the total amount distributed across all lines always adds up to exactly `amount` per
        plan. We try to correct for compounding rounding errors by assigning the exact outstanding amount when
        we detect that a line will close out a plan's total percentage. However, since multiple plans can be
        assigned to a line, with different prior distributions, there is the possible edge case that one line
        closes out two (or more) tallies with different compounding errors. This means there is no one correct
        amount that we can assign to a line that will correctly close out both all plans. This is described in
        more detail in the commit message, under "concurrent closing line edge case".
        """
        decimal_precision = self.env['decimal.precision'].precision_get('Percentage Analytic')
        distributed_percentage, distributed_amount = distribution_on_each_plan.get(self, (0, 0))
        allocated_percentage = distributed_percentage + percentage
        if float_compare(allocated_percentage, total_percentage, precision_digits=decimal_precision) == 0:
            calculated_amount = (amount * total_percentage / 100) - distributed_amount
        else:
            calculated_amount = amount * percentage / 100
        distributed_amount += float_round(calculated_amount, precision_digits=decimal_precision)
        distribution_on_each_plan[self] = (allocated_percentage, distributed_amount)
        return calculated_amount


class AccountAnalyticAccount(models.Model):
    _inherit = 'account.analytic.account'

    def _perform_analytic_distribution(self, distribution, amount, unit_amount, lines, obj, additive=False):
        """
        Redistributes the analytic lines to match the given distribution:
            - For account_ids where lines already exist, the amount and unit_amount of these lines get updated,
              lines where the updated amount becomes zero get unlinked.
            - For account_ids where lines don't exist yet, the line values to create them are returned,
              lines where the amount becomes zero are not included.

        :param distribution:    the desired distribution to match the analytic lines to
        :param amount:          the total amount to distribute over the analytic lines
        :param unit_amount:     the total unit amount (will not be distributed)
        :param lines:           the (current) analytic account lines that need to be matched to the new distribution
        :param obj:             the object on which _prepare_analytic_line_values(account_id, amount, unit_amount) will be
                                called to get the template for the values of new analytic line objects
        :param additive:        if True, the unit_amount and (distributed) amount get added to the existing lines

        :returns: a list of dicts containing the values for new analytic lines that need to be created
        :rtype:   dict
        """
        if not distribution:
            lines.unlink()
            return []

        # Does this: {'15': 40, '14,16': 60} -> { account(15): 40, account(14,16): 60 }
        distribution = {
            self.env['account.analytic.account'].browse(map(int, ids.split(','))).exists(): percentage
            for ids, percentage in distribution.items()
        }

        plans = self.env['account.analytic.plan']
        plans = sum(plans._get_all_plans(), plans)
        line_columns = [p._column_name() for p in plans]

        lines_to_link = []
        distribution_on_each_plan = {}
        total_percentages = {}

        for accounts, percentage in distribution.items():
            for plan in accounts.root_plan_id:
                total_percentages[plan] = total_percentages.get(plan, 0) + percentage

        for existing_aal in lines:
            # TODO: recommend something better for this line in review, please
            accounts = sum(map(existing_aal.mapped, line_columns), self.env['account.analytic.account'])
            if accounts in distribution:
                # Update the existing AAL for this account
                percentage = distribution[accounts]
                new_amount = 0
                new_unit_amount = unit_amount
                for account in accounts:
                    plan = account.root_plan_id
                    new_amount = plan._calculate_distribution_amount(amount, percentage, total_percentages[plan], distribution_on_each_plan)
                if additive:
                    new_amount += existing_aal.amount
                    new_unit_amount += existing_aal.unit_amount
                currency = accounts[0].currency_id or obj.company_id.currency_id
                if float_is_zero(new_amount, precision_rounding=currency.rounding):
                    existing_aal.unlink()
                else:
                    existing_aal.amount = new_amount
                    existing_aal.unit_amount = new_unit_amount
                # Prevent this distribution from being applied again
                del distribution[accounts]
            else:
                # Delete the existing AAL if it is no longer present in the new distribution
                existing_aal.unlink()
        # Create new lines from remaining distributions
        for accounts, percentage in distribution.items():
            if not accounts:
                continue
            account_field_values = {}
            for account in accounts:
                new_amount = account.root_plan_id._calculate_distribution_amount(amount, percentage, total_percentages[plan], distribution_on_each_plan)
                account_field_values[account.plan_id._column_name()] = account.id
            currency = account.currency_id or obj.company_id.currency_id
            if not float_is_zero(new_amount, precision_rounding=currency.rounding):
                lines_to_link.append(obj._prepare_analytic_line_values(account_field_values, new_amount, unit_amount))
        return lines_to_link
