# Copyright 2020 Tecnativa - David Vidal
# Copyright 2020 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import api, exceptions, fields, models
from odoo.tools.misc import formatLang


class SaleOrder(models.Model):
    _inherit = "sale.order"

    global_discount_ids = fields.Many2many(
        comodel_name="global.discount",
        string="Sale Global Discounts",
        domain="[('discount_scope', '=', 'sale'), "
        "('account_id', '!=', False), '|', "
        "('company_id', '=', company_id), ('company_id', '=', False)]",
        compute="_compute_global_discount_ids",
        store=True,
        readonly=False,
    )
    # HACK: Looks like UI doesn't behave well with Many2many fields and
    # negative groups when the same field is shown. In this case, we want to
    # show the readonly version to any not in the global discount group.
    # TODO: Check if it's fixed in future versions
    global_discount_ids_readonly = fields.Many2many(
        related="global_discount_ids",
        string="Sale Global Discounts (readonly)",
        readonly=True,
    )
    amount_global_discount = fields.Monetary(
        string="Total Global Discounts",
        compute="_compute_amounts",  # pylint: disable=C8108
        currency_field="currency_id",
        compute_sudo=True,  # Odoo core fields are storable so compute_sudo is True
        readonly=True,
        store=True,
    )
    amount_untaxed_before_global_discounts = fields.Monetary(
        string="Amount Untaxed Before Discounts",
        compute="_compute_amounts",  # pylint: disable=C8108
        currency_field="currency_id",
        compute_sudo=True,  # Odoo core fields are storable so compute_sudo is True
        readonly=True,
        store=True,
    )
    amount_total_before_global_discounts = fields.Monetary(
        string="Amount Total Before Discounts",
        compute="_compute_amounts",  # pylint: disable=C8108
        currency_field="currency_id",
        compute_sudo=True,  # Odoo core fields are storable so compute_sudo is True
        readonly=True,
        store=True,
    )

    @api.model
    def get_discounted_global(self, price=0, discounts=None):
        if not discounts:
            return price
        discounted_price = price
        for discount in discounts:
            discounted_price *= 1 - (discount / 100)
        return discounted_price

    def _check_global_discounts_sanity(self):
        """Perform a sanity check for discarding cases that will lead to
        incorrect data in discounts.
        """
        self.ensure_one()
        if not self.global_discount_ids:
            return True
        taxes_keys = {}
        for line in self.order_line.filtered(
            lambda _line: not _line.display_type
            and _line.product_id
            and not _line.product_id.bypass_global_discount
        ):
            if not line.tax_id:
                raise exceptions.UserError(
                    self.env._("With global discounts, taxes in lines are required.")
                )
            for key in taxes_keys:
                if key == line.tax_id:
                    break
                elif key & line.tax_id:
                    raise exceptions.UserError(
                        self.env._("Incompatible taxes found for global discounts.")
                    )
            else:
                taxes_keys[line.tax_id] = True

    @api.depends(
        "order_line.product_id.bypass_global_discount",
        "order_line.price_subtotal",
        "order_line.price_tax",
        "order_line.price_total",
        "global_discount_ids",
    )
    def _compute_amounts(self):
        res = super()._compute_amounts()
        for order in self:
            order._check_global_discounts_sanity()
            amount_untaxed_before_global_discounts = order.amount_untaxed
            amount_total_before_global_discounts = order.amount_total
            discounts = order.global_discount_ids.mapped("discount")
            amount_discounted_untaxed = amount_discounted_tax = 0
            for line in order.order_line:
                discounted_subtotal = line.price_subtotal
                if not line.product_id.bypass_global_discount:
                    discounted_subtotal = self.get_discounted_global(
                        line.price_subtotal, discounts.copy()
                    )
                amount_discounted_untaxed += discounted_subtotal
                discounted_tax = line.tax_id.with_context(
                    force_price_include=False
                ).compute_all(
                    discounted_subtotal,
                    line.order_id.currency_id,
                    1.0,
                    product=line.product_id,
                    partner=line.order_id.partner_shipping_id,
                )
                amount_discounted_tax += sum(
                    t.get("amount", 0.0) for t in discounted_tax.get("taxes", [])
                )
            order.update(
                {
                    "amount_untaxed_before_global_discounts": (
                        amount_untaxed_before_global_discounts
                    ),
                    "amount_total_before_global_discounts": (
                        amount_total_before_global_discounts
                    ),
                    "amount_global_discount": (
                        amount_untaxed_before_global_discounts
                        - amount_discounted_untaxed
                    ),
                    "amount_untaxed": amount_discounted_untaxed,
                    "amount_tax": amount_discounted_tax,
                    "amount_total": (amount_discounted_untaxed + amount_discounted_tax),
                }
            )
        return res

    def _compute_tax_totals(self):
        res = super()._compute_tax_totals()
        for order in self:
            amount_discount_by_group = {}
            cumulative_discount_rate = 1.0
            currency = order.currency_id
            # Calculate cumulative discount rate
            for gbl_disc in order.global_discount_ids:
                discount_rate = gbl_disc.discount / 100
                cumulative_discount_rate *= 1 - discount_rate
            base_amount = 0.0
            # Calculate the total discount amount and discount by tax group
            for line in order.order_line:
                if line.display_type or not line.product_id:
                    continue
                # Apply cumulative discount rate only if bypass_global_discount is False
                if not line.product_id.bypass_global_discount:
                    discounted_price_subtotal = (
                        line.price_subtotal * cumulative_discount_rate
                    )
                else:
                    discounted_price_subtotal = line.price_subtotal
                base_amount += discounted_price_subtotal
                # Calculate tax amounts for each tax group based on the discounted
                # subtotal
                for tax in line.tax_id:
                    tax_group_id = tax.tax_group_id.id
                    if tax_group_id not in amount_discount_by_group:
                        amount_discount_by_group[tax_group_id] = 0.0
                    # Compute taxes on the correct base amount
                    discounted_tax_vals = tax.with_context(
                        force_price_include=False
                    ).compute_all(
                        discounted_price_subtotal,
                        currency,
                        1.0,
                        product=line.product_id,
                        partner=order.partner_shipping_id,
                    )
                    amount_discount_by_group[tax_group_id] += sum(
                        t.get("amount", 0.0)
                        for t in discounted_tax_vals.get("taxes", [])
                    )
            # Calculate the final amount total
            total_amount = base_amount + sum(amount_discount_by_group.values())
            base_amount_currency = formatLang(
                self.env, base_amount, currency_obj=currency
            )
            order.tax_totals.update(
                {
                    "base_amount": base_amount,
                    "total_amount": total_amount,
                    "base_amount_currency": base_amount,
                    "total_amount_currency": total_amount,
                }
            )
            # Update subtotals and groups by subtotal
            for group in order.tax_totals["subtotals"]:
                group.update(
                    {
                        "base_amount": base_amount,
                        "base_amount_currency": base_amount,
                        "amount": base_amount,
                        "formatted_amount": base_amount_currency,
                    }
                )
                for tax_group in group["tax_groups"]:
                    discounted_tax_amount = amount_discount_by_group.get(
                        tax_group["id"], 0.0
                    )
                    tax_group.update(
                        {
                            "base_amount": base_amount,
                            "base_amount_currency": base_amount,
                            "tax_amount": discounted_tax_amount,
                            "tax_amount_currency": discounted_tax_amount,
                        }
                    )
        return res

    @api.depends("partner_id", "company_id")
    def _compute_global_discount_ids(self):
        for order in self:
            commercial_global_disc = (
                order.partner_id.commercial_partner_id.customer_global_discount_ids
            )
            _discounts = (
                commercial_global_disc
                if commercial_global_disc
                else order.partner_id.customer_global_discount_ids
            )
            discounts = self.env["global.discount"]
            for discount in _discounts:
                if discount.company_id == order.company_id:
                    discounts |= discount
            order.global_discount_ids = discounts

    def _prepare_invoice(self):
        invoice_vals = super()._prepare_invoice()
        if self.global_discount_ids:
            invoice_vals.update(
                {"global_discount_ids": [(6, 0, self.global_discount_ids.ids)]}
            )
        return invoice_vals
