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

from odoo import api, fields, models
from odoo.tools import float_compare


class Rma(models.Model):
    _inherit = "rma"

    order_id = fields.Many2one(
        comodel_name="sale.order",
        string="Sale Order",
        domain="["
        "    ('partner_id', 'child_of', commercial_partner_id),"
        "    ('state', 'in', ['sale', 'done']),"
        "]",
        store=True,
        readonly=False,
        compute="_compute_order_id",
    )
    allowed_picking_ids = fields.Many2many(
        comodel_name="stock.picking",
        compute="_compute_allowed_picking_ids",
    )
    picking_id = fields.Many2one(
        domain="(order_id or partner_id) and [('id', 'in', allowed_picking_ids)] or "
        "[('state', '=', 'done'), ('picking_type_id.code', '=', 'outgoing')] "
    )
    allowed_move_ids = fields.Many2many(
        comodel_name="stock.move",
        compute="_compute_allowed_move_ids",
    )
    move_id = fields.Many2one(domain="[('id', 'in', allowed_move_ids)]")
    sale_line_id = fields.Many2one(
        related="move_id.sale_line_id",
    )
    allowed_product_ids = fields.Many2many(
        comodel_name="product.product",
        compute="_compute_allowed_product_ids",
    )
    product_id = fields.Many2one(
        domain="order_id and [('id', 'in', allowed_product_ids)] or "
        "[('type', 'in', ['consu', 'product'])]"
    )
    # Add index to this field, as we perform a search on it
    refund_id = fields.Many2one(index=True)

    @api.depends("partner_id", "order_id")
    def _compute_allowed_picking_ids(self):
        domain = [("state", "=", "done"), ("picking_type_id.code", "=", "outgoing")]
        for rec in self:
            domain2 = domain.copy()
            if rec.partner_id:
                commercial_partner = rec.partner_id.commercial_partner_id
                domain2.append(("partner_id", "child_of", commercial_partner.id))
            if rec.order_id:
                domain2.append(("sale_id", "=", rec.order_id.id))
            if domain2 != domain:
                rec.allowed_picking_ids = self.env["stock.picking"].search(domain2)
            else:
                rec.allowed_picking_ids = False  # don't populate a big list

    @api.depends("order_id", "picking_id")
    def _compute_allowed_move_ids(self):
        for rec in self:
            if rec.order_id:
                order_move = rec.order_id.order_line.mapped("move_ids")
                rec.allowed_move_ids = order_move.filtered(
                    lambda r: r.picking_id == self.picking_id and r.state == "done"
                ).ids
            else:
                rec.allowed_move_ids = self.picking_id.move_ids.ids

    @api.depends("order_id")
    def _compute_allowed_product_ids(self):
        for rec in self:
            if rec.order_id:
                order_product = rec.order_id.order_line.mapped("product_id")
                rec.allowed_product_ids = order_product.filtered(
                    lambda r: r.type in ["consu", "product"]
                ).ids
            else:
                rec.allowed_product_ids = False  # don't populate a big list

    @api.depends("partner_id")
    def _compute_order_id(self):
        """Empty sales order when changing partner."""
        self.order_id = False

    @api.onchange("order_id")
    def _onchange_order_id(self):
        self.product_id = self.picking_id = False

    def _link_refund_with_reception_move(self):
        """Perform the internal operations for linking the RMA reception move with the
        sales order line if applicable.
        """
        self.ensure_one()
        move = self.reception_move_id
        if (
            move
            and float_compare(
                self.product_uom_qty,
                move.product_uom_qty,
                precision_rounding=move.product_uom.rounding,
            )
            == 0
        ):
            self.reception_move_id.sale_line_id = self.sale_line_id.id
            self.reception_move_id.to_refund = True

    def _unlink_refund_with_reception_move(self):
        """Perform the internal operations for unlinking the RMA reception move with the
        sales order line.
        """
        self.ensure_one()
        self.reception_move_id.sale_line_id = False
        self.reception_move_id.to_refund = False

    def action_refund(self):
        """As we have made a refund, the return move + the refund should be linked to
        the source sales order line, to decrease both the delivered and invoiced
        quantity.

        NOTE: The refund line is linked to the SO line in `_prepare_refund_line`.
        """
        res = super().action_refund()
        for rma in self:
            if rma.sale_line_id:
                rma._link_refund_with_reception_move()
        return res

    def _prepare_refund_vals(self, origin=False):
        """Inject salesman from sales order (if any)"""
        vals = super()._prepare_refund_vals(origin=origin)
        if self.order_id:
            vals["invoice_user_id"] = self.order_id.user_id.id
        return vals

    def _prepare_refund_line_vals(self):
        """Add line data and link to the sales order, only if the RMA is for the whole
        move quantity. In other cases, incorrect delivered/invoiced quantities will be
        logged on the sales order, so better to let the operations not linked.
        """
        vals = super()._prepare_refund_line_vals()
        line = self.sale_line_id
        if line:
            vals["product_id"] = line.product_id.id
            vals["price_unit"] = line.price_unit
            vals["discount"] = line.discount
            vals["sequence"] = line.sequence
            move = self.reception_move_id
            if (
                move
                and float_compare(
                    self.product_uom_qty,
                    move.product_uom_qty,
                    precision_rounding=move.product_uom.rounding,
                )
                == 0
            ):
                vals["sale_line_ids"] = [(4, line.id)]
        return vals

    def _prepare_procurement_group_vals(self):
        vals = super()._prepare_procurement_group_vals()
        if not self.env.context.get("ignore_rma_sale_order") and self.order_id:
            vals["sale_id"] = self.order_id.id
        return vals

    def _prepare_delivery_procurements(self, scheduled_date=None, qty=None, uom=None):
        self = self.with_context(ignore_rma_sale_order=True)
        return super()._prepare_delivery_procurements(
            scheduled_date=scheduled_date, qty=qty, uom=uom
        )

    def _prepare_delivery_procurement_vals(self, scheduled_date=None):
        vals = super()._prepare_delivery_procurement_vals(scheduled_date=scheduled_date)
        if (
            self.move_id
            and self.move_id.sale_line_id
            and self.operation_id.action_create_refund == "update_quantity"
        ):
            vals["sale_line_id"] = self.move_id.sale_line_id.id
        return vals

    def _prepare_replace_procurement_vals(self, warehouse=None, scheduled_date=None):
        vals = super()._prepare_replace_procurement_vals(
            warehouse=warehouse, scheduled_date=scheduled_date
        )
        if (
            self.move_id
            and self.move_id.sale_line_id
            and self.operation_id.action_create_refund == "update_quantity"
        ):
            vals["sale_line_id"] = self.move_id.sale_line_id.id
        return vals

    def _prepare_reception_procurement_vals(self, group=None):
        """This method is used only for reception and a specific RMA IN route."""
        vals = super()._prepare_reception_procurement_vals(group=group)
        if (
            self.move_id
            and self.move_id.sale_line_id
            and self.operation_id.action_create_refund == "update_quantity"
        ):
            vals["sale_line_id"] = self.move_id.sale_line_id.id
        return vals

    def create_replace(self, scheduled_date, warehouse, product, qty, uom):
        # When the procurement group has the sale id set it will propagate to the
        # pickings. This is inconvenient for this operation as when we confirm the
        # customer delivery a new order line will be created with the replaced option
        # which will be set for invoicing.
        moves_before = self.delivery_move_ids
        res = super().create_replace(scheduled_date, warehouse, product, qty, uom)
        new_moves = self.delivery_move_ids - moves_before
        new_moves.picking_id.sale_id = False
        return res
