# Copyright (C) 2020-TODAY KMEE
# Copyright (C) 2021-TODAY Akretion
# @author Magno Costa <magno.costa@akretion.com.br>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import api, fields, models


class StockInvoiceOnshipping(models.TransientModel):
    _inherit = "stock.invoice.onshipping"

    @api.model
    def _default_has_down_payment(self):
        pickings = self._load_pickings()
        sale_pickings = pickings.filtered(lambda pk: pk.sale_id)
        downpayment_lines = False
        if sale_pickings:
            for pick in sale_pickings:
                # order = pick.sale_id
                # sale_lines = order.mapped("order_line")
                if pick.sale_id.order_line.filtered(lambda ln: ln.is_downpayment):
                    downpayment_lines = True

        return downpayment_lines

    deduct_down_payments = fields.Boolean("Deduct down payments", default=True)
    has_down_payments = fields.Boolean(
        "Has down payments", default=_default_has_down_payment
    )

    def _get_fields_not_used_from_sale(self):
        """Fields not used from Sale 'prepare' method"""
        # For reference, fields get from 'prepare' method
        #   "ref": self.client_order_ref or ''
        #   "narration": self.note,
        #   "campaign_id": self.campaign_id.id,
        #   "medium_id": self.medium_id.id,
        #   "source_id": self.source_id.id,
        #   "team_id": self.team_id.id,
        #   "partner_shipping_id": self.partner_shipping_id.id,
        #   "partner_bank_id": self.company_id.partner_id.bank_ids.
        #      filtered(lambda bank: bank.company_id.id in
        #      (self.company_id.id, False))[:1].id,
        #   "invoice_payment_term_id": self.payment_term_id.id,
        #   "payment_reference": self.reference,
        #   "transaction_ids": [(6, 0, self.transaction_ids.ids)],

        return {
            "move_type",
            "currency_id",
            "user_id",
            "invoice_user_id",
            "partner_id",
            "fiscal_position_id",
            "journal_id",  # company comes from the journal
            "invoice_origin",
            "invoice_line_ids",
            "company_id",
            # Another fields
            "__last_update",
            "display_name",
        }

    def _build_invoice_values_from_pickings(self, pickings):
        invoice, values = super()._build_invoice_values_from_pickings(pickings)

        sale_pickings = pickings.filtered(lambda pk: pk.sale_id)
        # Refund case don't get values from Sale Dict
        # TODO: Should get any value?
        if sale_pickings and self._get_invoice_type() != "out_refund":
            # Case more than one Sale Order the fields below will be join
            # the others will be overwritting, as done in sale module,
            # one more field include here Note/Narration
            payment_refs = set()
            refs = set()
            # Include Narration
            narration = set()
            for pick in sale_pickings.sorted(key=lambda p: p.name):
                # Other modules can included new fields in Sale Order and include
                # this fields in the dict of creation Invoice from sale, for
                # example:
                # - account_payment_sale
                #  https://github.com/OCA/bank-payment/blob/14.0/
                #  account_payment_sale/models/sale_order.py#L41
                # - sale_commssion
                #  https://github.com/OCA/commission/blob/14.0/
                #  sale_commission/models/sale_order.py#L64
                # To avoid the necessity of a 'glue' module the method get the
                # values from _prepare_invoice but removed some fields of the
                # original method, given priority for values from
                # stock_picking_invoicing dict, for now it's seems the best to
                # way to avoid the 'glue' modules problem.
                sale_values = pick.sale_id._prepare_invoice()
                # Fields to Join
                # origins.add(sale_values["invoice_origin"])
                payment_refs.add(sale_values["payment_reference"])
                refs.add(sale_values["ref"])
                narration.add(sale_values["narration"])

                sale_values_rm = {
                    k: sale_values[k]
                    for k in set(sale_values) - self._get_fields_not_used_from_sale()
                }

                values.update(sale_values_rm)

            # Fields to join
            if len(sale_pickings) > 1:
                values.update(
                    {
                        "ref": ", ".join(refs)[:2000],
                        # In this case Origin get Pickings Names
                        # "invoice_origin": ", ".join(origins),
                        "payment_reference": len(payment_refs) == 1
                        and payment_refs.pop()
                        or False,
                        "narration": ", ".join(narration),
                    }
                )

        return invoice, values

    # Check the comment below
    # def _get_picking_key(self, picking):
    #    key = super()._get_picking_key(picking)
    #    if picking.sale_id:
    #        key = key + (
    #            picking.sale_id.payment_term_id,
    #            picking.sale_id.fiscal_position_id,
    #            picking.sale_id.commitment_date,
    #            picking.sale_id.analytic_account_id,
    #            picking.sale_id.pricelist_id,
    #            picking.sale_id.company_id,
    #        )
    #    return key

    def _get_move_key(self, move):
        key = super()._get_move_key(move)
        if move.sale_line_id:
            # TODO: Analise if Sale Lines should be grouped.
            #  For now remains a problem https://github.com/odoo/odoo/pull/77195
            #  with field qty_invoiced at sale.order.line, when a invoice line
            #  has more than one sale line related the field show the total QTY
            #  of those lines, e.g:
            #  product_uom_qty | qty_invoiced
            #       2.0        |  4.0
            # key = key + (
            #    move.sale_line_id.price_unit,
            #    move.sale_line_id.customer_lead,
            #    move.sale_line_id.currency_id,
            #    move.sale_line_id.tax_id,
            #    move.sale_line_id.analytic_tag_ids,
            # )
            key = key + (move.sale_line_id,)

        return key

    def _get_fields_not_used_from_sale_line(self):
        """Fields not used from Sale Line 'prepare' method"""
        # Original fields from sale module
        # Fields do get
        #     "sequence": self.sequence,
        #     "discount": self.discount,
        #     "display_type": self.display_type or 'product'
        #     "is_downpayment": self.is_downpayment
        #     * optional_values
        #     * 'N field' included in _prepare_invoice_line method
        #        by another module

        return {
            # Fields from Move has priority
            "name",
            "product_id",
            "product_uom_id",
            "quantity",
            "price_unit",
            "tax_ids",
            # Already get
            "sale_line_ids",
            "anlytic_distribution",
            # another fields
            "__last_update",
            "display_name",
        }

    def _get_invoice_line_values(self, moves, invoice_values, invoice):
        values = super()._get_invoice_line_values(moves, invoice_values, invoice)
        move = fields.first(moves)
        if move.sale_line_id:
            # Same make above, get fields informed in Sale Line dict
            sale_line_values = move.sale_line_id._prepare_invoice_line()
            # Vals informed in any case
            values["sale_line_ids"] = [(6, 0, moves.sale_line_id.ids)]
            values["analytic_distribution"] = sale_line_values.get(
                "analytic_distribution"
            )
            # Refund case don't get values from Sale Line Dict
            # TODO: Should get any value?
            if self._get_invoice_type() != "out_refund":
                # Same make above, get fields informed in Sale Line dict
                sale_line_values = move.sale_line_id._prepare_invoice_line()

                sale_line_values_rm = {
                    k: sale_line_values[k]
                    for k in set(sale_line_values)
                    - self._get_fields_not_used_from_sale_line()
                }
                values.update(sale_line_values_rm)

        return values

    def _get_pickings_with_sale(self, invoice_values):
        pickings = self._load_pickings()

        # Filter Picking with Sales
        picking_in_invoice_values = self.env["stock.picking"]
        for line in invoice_values.get("invoice_line_ids"):
            if line[2]:
                if len(line[2].get("move_line_ids")[0]) == 2:
                    # [(4, 233)],
                    move_line_id = line[2].get("move_line_ids")[0][1]
                else:
                    # [(<Command.SET: 6>, 0, [51])]
                    move_line_id = line[2].get("move_line_ids")[0][2]

                move_line = self.env["stock.move"].browse(move_line_id)
                picking_in_invoice_values |= move_line.mapped("picking_id")

        sale_pickings = pickings.filtered(
            lambda pk: pk.sale_id
            # Check Sales Ungrouped
            and pk.id in picking_in_invoice_values.ids
        )

        return sale_pickings

    def _create_invoice(self, invoice_values):
        """Override this method if you need to change any values of the
        invoice and the lines before the invoice creation
        :param invoice_values: dict with the invoice and its lines
        :return: invoice
        """
        sale_pickings = self._get_pickings_with_sale(invoice_values)

        # Refund case don't included Section, Note or DownPayments
        if not sale_pickings or self._get_invoice_type() == "out_refund":
            return super()._create_invoice(invoice_values)

        # Check Other Sale Lines
        # Section, Note and Down Payments
        section_note_lines = down_payment_lines = self.env["sale.order.line"]
        # Resequencing
        invoice_item_sequence = (
            0  # Incremental sequencing to keep the lines order on the invoice.
        )
        invoice_item_seq_dict = {}
        for pick in sale_pickings.sorted(key=lambda p: p.name):
            order = pick.sale_id.with_company(pick.sale_id.company_id)
            invoiceable_lines = order._get_invoiceable_lines(final=True)
            section_note_lines |= invoiceable_lines.filtered(
                lambda ln: ln.display_type in ("line_section", "line_note")
            )
            down_payment_lines |= invoiceable_lines.filtered(
                lambda ln: ln.is_downpayment
            )

            # Use for Resequencing
            for line in order.order_line:
                invoice_item_seq_dict[line.id] = invoice_item_sequence
                invoice_item_sequence += 1

        # Sections and Notes
        if section_note_lines:
            section_note_vals = []
            for line in section_note_lines:
                sale_line_vals = line._prepare_invoice_line()
                # Change [(4, 59)] for [(6, 0, [59])] to avoid error
                # in method to Resequencing
                sale_line_vals["sale_line_ids"] = [
                    (6, 0, [sale_line_vals.get("sale_line_ids")[0][1]])
                ]
                section_note_vals.append((0, 0, sale_line_vals))

            invoice_values["invoice_line_ids"] += section_note_vals

        # Resequencing, necessary in the case of Grouping Sale Orders
        for line in invoice_values.get("invoice_line_ids"):
            # [(6, 0, {})]
            if line[2]:
                sale_line = line[2].get("sale_line_ids")
                if sale_line:
                    # [(6, 0, [58])]
                    line[2]["sequence"] = invoice_item_seq_dict.get(sale_line[0][2][0])

        # Down Payments
        # After the Resequencing to put it in the end of Invoice
        if down_payment_lines:
            down_payment_vals = []
            down_payment_section_added = False
            for line in down_payment_lines:
                if not down_payment_section_added and line.is_downpayment:
                    # Create a dedicated section for the down payments
                    # (put at the end of the invoiceable_lines)
                    down_payment_vals.append(
                        (
                            0,
                            0,
                            line.order_id._prepare_down_payment_section_line(
                                sequence=invoice_item_sequence,
                            ),
                        ),
                    )
                    down_payment_section_added = True
                    invoice_item_sequence += 1

                if line.is_downpayment:
                    down_payment_vals.append(
                        (
                            0,
                            0,
                            line._prepare_invoice_line(
                                sequence=invoice_item_sequence,
                            ),
                        ),
                    )
                    invoice_item_sequence += 1

            invoice_values["invoice_line_ids"] += down_payment_vals

        moves = (
            self.env["account.move"]
            .sudo()
            .with_context(default_move_type="out_invoice")
            .create(invoice_values)
        )

        # param Final: if True, refunds will be generated if necessary
        final = self.deduct_down_payments
        if final:
            moves.sudo().filtered(
                lambda m: m.amount_total < 0
            ).action_switch_move_type()
        for move in moves:
            move.message_post_with_source(
                source_ref="mail.message_origin_link",
                render_values={
                    "self": move.picking_ids,
                    "origin": move.picking_ids,
                },
                subtype_id=self.env.ref("mail.mt_note").id,
            )
        return moves
