# 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 exceptions
from odoo.tests import Form

from odoo.addons.stock_picking_invoicing.tests.common import TestPickingInvoicingCommon


class TestSaleStock(TestPickingInvoicingCommon):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.account_move_model = cls.env["account.move"]
        cls.invoice_wizard = cls.env["stock.invoice.onshipping"]
        cls.stock_return_picking = cls.env["stock.return.picking"]
        cls.stock_picking = cls.env["stock.picking"]
        # In order to avoid errors in the tests CI environment when the tests
        # Create of Invoice by Sale Order using sale.advance.payment.inv object
        # is necessary let default policy as sale_order, just affect demo data.
        # TODO: Is there other form to avoid this problem?
        cls.companies = cls.env["res.company"].search(
            [("sale_invoicing_policy", "=", "sale_order")]
        )
        for company in cls.companies:
            company.sale_invoicing_policy = "stock_picking"

    def test_01_sale_stock_return(self):
        """
        Test a SO with a product invoiced on delivery. Deliver and invoice
        the SO, then do a return of the picking. Check that a refund
         invoice is well generated.
        """
        # intial so
        self.partner = self.env.ref(
            "sale_stock_picking_invoicing.res_partner_2_address"
        )
        self.product = self.env.ref("product.product_delivery_01")
        so_vals = {
            "partner_id": self.partner.id,
            "partner_invoice_id": self.partner.id,
            "partner_shipping_id": self.partner.id,
            "order_line": [
                (
                    0,
                    0,
                    {
                        "name": self.product.name,
                        "product_id": self.product.id,
                        "product_uom_qty": 3.0,
                        "product_uom": self.product.uom_id.id,
                        "price_unit": self.product.list_price,
                    },
                )
            ],
            "pricelist_id": self.env.ref(
                "sale_stock_picking_invoicing.demo_pricelist"
            ).id,
        }
        self.so = self.env["sale.order"].create(so_vals)

        # confirm our standard so, check the picking
        self.so.action_confirm()
        self.assertTrue(
            self.so.picking_ids,
            'Sale Stock: no picking created for "invoice on '
            'delivery" storable products',
        )

        # set stock.picking to be invoiced
        self.assertTrue(
            len(self.so.picking_ids) == 1,
            "More than one stock " "picking for sale.order",
        )

        # Check Sale Invoicing Policy Warning to force create Invoice from Picking
        with self.assertRaises(exceptions.UserError):
            self.so._create_invoices(final=True)

        self.so.picking_ids.set_to_be_invoiced()

        # validate stock.picking
        stock_picking = self.so.picking_ids

        # compare sale.order.line with stock.move
        stock_move = stock_picking.move_ids
        sale_order_line = self.so.order_line

        sm_fields = [key for key in self.env["stock.move"]._fields.keys()]
        sol_fields = [key for key in self.env["sale.order.line"]._fields.keys()]

        skipped_fields = [
            "id",
            # 'S00029/FURN_7777: Stock>Customers' != 'S00029 - Office Chair'
            "display_name",
            "state",
            # Price Unit in move is different from sale line
            # TODO: Should be equal? After Confirmed stock picking
            #  the value will be change based Stock Valuation
            #  configuration.
            "price_unit",
            # There are a diference for field Name
            # '[FURN_7777] Office Chair' != 'Office Chair'
            "name",
        ]
        common_fields = list(set(sm_fields) & set(sol_fields) - set(skipped_fields))

        for field in common_fields:
            self.assertEqual(
                stock_move[field],
                sale_order_line[field],
                f"Field {field} failed to transfer from sale.order.line "
                "to stock.move",
            )

    def test_picking_sale_order_product_and_service(self):
        """
        Test Sale Order with product and service
        """

        # Ensure the company's sale_invoicing_policy is set to "stock_picking"
        company = self.env.ref("base.main_company")
        company.sale_invoicing_policy = "stock_picking"
        sale_order_form = sale_order_form = Form(
            self.env.ref("sale_stock_picking_invoicing.main_company-sale_order_2")
        )
        # Necessary to get the currency
        sale_order_form.pricelist_id = self.env.ref(
            "sale_stock_picking_invoicing.demo_pricelist"
        )
        sale_order_2 = sale_order_form.save()
        sale_order_2.action_confirm()
        # Method to create invoice in sale order should work only
        # for lines where products are of TYPE Service
        sale_order_2._create_invoices()
        # Should be exist one Invoice
        self.assertEqual(1, sale_order_2.invoice_count)
        for invoice in sale_order_2.invoice_ids:
            line = invoice.invoice_line_ids.filtered(
                lambda ln: ln.product_id.type == "service"
            )
            self.assertEqual(line.product_id.type, "service")
            # Invoice of Service
            invoice.action_post()
            self.assertEqual(
                invoice.state, "posted", "Invoice should be in state Posted"
            )

        picking = sale_order_2.picking_ids
        # Only the line of Type Product
        self.assertEqual(len(picking.move_ids_without_package), 1)
        self.assertEqual(picking.invoice_state, "2binvoiced")
        self.picking_move_state(picking)

        # Test Create Invoice from Sale when raise UseError
        context = {
            "active_model": "sale.order",
            "active_id": sale_order_2.id,
            "active_ids": sale_order_2.ids,
        }
        payment = (
            self.env["sale.advance.payment.inv"]
            .with_context(**context)
            .create(
                {
                    "advance_payment_method": "delivered",
                }
            )
        )
        with self.assertRaises(exceptions.UserError):
            payment.with_context(**context).create_invoices()

        invoice = self.create_invoice_wizard(picking)
        self.assertEqual(picking.invoice_state, "invoiced")
        self.assertIn(invoice, picking.invoice_ids)
        self.assertIn(picking, invoice.picking_ids)
        # Picking with Partner Shipping from Sale Order
        self.assertEqual(picking.partner_id, sale_order_2.partner_shipping_id)
        # Invoice created with Partner Invoice from Sale Order
        self.assertEqual(invoice.partner_id, sale_order_2.partner_invoice_id)
        # Invoice created with Partner Shipping from Picking
        self.assertEqual(invoice.partner_shipping_id, picking.partner_id)
        # When informed Payment Term in Sale Orde should be
        # used instead of the default in Partner.
        self.assertEqual(invoice.invoice_payment_term_id, sale_order_2.payment_term_id)

        # 1 Product 1 Note should be created
        self.assertEqual(len(invoice.invoice_line_ids), 2)

        # In the Sale Order should be exist two Invoices, one
        # for Product other for Service
        self.assertEqual(2, sale_order_2.invoice_count)

        # Confirm Invoice
        invoice.action_post()
        self.assertEqual(invoice.state, "posted", "Invoice should be in state Posted.")

        # Check Invoiced QTY
        for line in sale_order_2.order_line.filtered(
            lambda ln: ln.product_id.type == "product"
        ):
            self.assertEqual(line.product_uom_qty, line.qty_invoiced)
            # Test the qty_to_invoice
            line.product_id.invoice_policy = "order"
            self.assertEqual(line.qty_to_invoice, 0.0)

        # Check if the Sale Lines fields are equals to Invoice Lines
        sol_fields = [key for key in self.env["sale.order.line"]._fields.keys()]

        acl_fields = [key for key in self.env["account.move.line"]._fields.keys()]

        skipped_fields = [
            "id",
            "display_name",
            "state",
            "create_date",
            # By th TAX 15% automatic add in invoice the value change
            "price_total",
            # Necessary after call onchange_partner_id
            "write_date",
            "__last_update",
            # Field sequence add in creation of Invoice
            "sequence",
            # In the sale.orde.line display_type has only line_section
            # and line_note, the acccount.move.line has more options
            "display_type",
        ]

        common_fields = list(set(acl_fields) & set(sol_fields) - set(skipped_fields))
        sale_order_line = picking.move_ids_without_package.filtered(
            lambda ln: ln.sale_line_id
        ).sale_line_id
        invoice_lines = picking.invoice_ids.invoice_line_ids.filtered(
            lambda ln: ln.product_id
        )
        # Necessary for get analytic_precision
        # this problem only occours in the tests, by some reason not
        # identify yet, but works in the screen the default behavior
        with Form(invoice_lines) as line:
            line.save()

        for field in common_fields:
            self.assertEqual(
                sale_order_line[field],
                invoice_lines[field],
                f"Field {field} failed to transfer from sale.order.line "
                "to account.invoice.line",
            )

        # Return Picking
        picking_devolution = self.return_picking_wizard(picking)

        self.assertEqual(picking_devolution.invoice_state, "2binvoiced")
        for line in picking_devolution.move_ids:
            self.assertEqual(line.invoice_state, "2binvoiced")

        self.picking_move_state(picking_devolution)
        self.assertEqual(picking_devolution.state, "done", "Change state fail.")

        invoice_devolution = self.create_invoice_wizard(picking_devolution)
        # Confirm Invoice
        invoice_devolution.action_post()
        self.assertEqual(
            invoice_devolution.state, "posted", "Invoice should be in state Posted"
        )
        # Test need to be comment because there are a problem with module
        # sale_line_refund_to_invoice_qty
        # https://github.com/OCA/account-invoicing/blob/
        # 14.0/sale_line_refund_to_invoice_qty/models/sale.py#L20
        # when the tests run in CI of the repo the test fail.
        # TODO: The module should be compatible with this case?
        # Check Invoiced QTY update after Refund
        # for line in sale_order_2.order_line:
        #    # Check Product line
        #    if line.product_id.type == "product":
        #        # self.assertEqual(0.0, line.qty_invoiced)

    def test_picking_invoicing_partner_shipping_invoiced(self):
        """
        Test the invoice generation grouped by partner/product with 2
        picking and 2 moves per picking, but Partner to Shipping is
        different from Partner to Invoice.
        """
        sale_order_1 = self.env.ref(
            "sale_stock_picking_invoicing.main_company-sale_order_1"
        )
        sale_order_1.action_confirm()
        picking = sale_order_1.picking_ids
        self.picking_move_state(picking)
        sale_order_2 = self.env.ref(
            "sale_stock_picking_invoicing.main_company-sale_order_2"
        )
        sale_order_2.action_confirm()
        picking2 = sale_order_2.picking_ids
        self.picking_move_state(picking2)
        pickings = picking | picking2
        invoice = self.create_invoice_wizard(pickings)
        # Groupping Invoice
        self.assertEqual(len(invoice), 1)
        # Invoice should be create with the partner_invoice_id
        self.assertEqual(invoice.partner_id, sale_order_1.partner_invoice_id)
        # Invoice partner shipping should be the same of picking
        self.assertEqual(invoice.partner_shipping_id, picking.partner_id)
        self.assertIn(invoice, picking.invoice_ids)
        self.assertIn(picking, invoice.picking_ids)
        self.assertIn(invoice, picking2.invoice_ids)
        self.assertIn(picking2, invoice.picking_ids)

        # TODO: Grouping sale line with KEY should be analise
        # self.assertEqual(len(invoice.invoice_line_ids), 2)
        # 3 Products, 2 Note and 2 Section
        self.assertEqual(len(invoice.invoice_line_ids), 7)
        for inv_line in invoice.invoice_line_ids.filtered(lambda ln: ln.product_id):
            self.assertTrue(inv_line.tax_ids, "Error to map Sale Tax in invoice.line.")
        # Post the Invoice to validate the fields
        invoice.action_post()

    def test_ungrouping_pickings_partner_shipping_different(self):
        """
        Test the invoice generation grouped by partner/product with 3
        picking and 2 moves per picking, the 3 has the same Partner to
        Invoice but one has Partner to Shipping so shouldn't be grouping.
        """

        sale_order_1 = self.env.ref(
            "sale_stock_picking_invoicing.main_company-sale_order_1"
        )
        sale_order_1.action_confirm()
        picking = sale_order_1.picking_ids
        self.picking_move_state(picking)

        sale_order_3 = self.env.ref(
            "sale_stock_picking_invoicing.main_company-sale_order_3"
        )
        sale_order_3.action_confirm()
        picking3 = sale_order_3.picking_ids
        self.picking_move_state(picking3)

        sale_order_4 = self.env.ref(
            "sale_stock_picking_invoicing.main_company-sale_order_4"
        )
        sale_order_4.action_confirm()
        picking4 = sale_order_4.picking_ids
        self.picking_move_state(picking4)

        pickings = picking | picking3 | picking4
        invoices = self.create_invoice_wizard(pickings)
        # Even with same Partner Invoice if the Partner Shipping
        # are different should not be Groupping
        self.assertEqual(len(invoices), 2)

        # Invoice that has different Partner Shipping
        # should be not groupping
        invoice_pick_1 = invoices.filtered(
            lambda t: t.partner_id != t.partner_shipping_id
        )
        # Invoice should be create with partner_invoice_id
        self.assertEqual(invoice_pick_1.partner_id, sale_order_1.partner_invoice_id)
        # Invoice create with Partner Shipping used in Picking
        self.assertEqual(invoice_pick_1.partner_shipping_id, picking.partner_id)

        # Groupping Invoice
        invoice_pick_3_4 = invoices.filtered(
            lambda t: t.partner_id == t.partner_shipping_id
        )
        self.assertIn(invoice_pick_3_4, picking3.invoice_ids)
        self.assertIn(invoice_pick_3_4, picking4.invoice_ids)

    def test_down_payment(self):
        """Test the case with Down Payment"""
        sale_order_1 = self.env.ref(
            "sale_stock_picking_invoicing.main_company-sale_order_1"
        )
        sale_order_1.action_confirm()
        # Create Invoice Sale
        context = {
            "active_model": "sale.order",
            "active_id": sale_order_1.id,
            "active_ids": sale_order_1.ids,
        }
        # DownPayment
        payment_wizard = (
            self.env["sale.advance.payment.inv"]
            .with_context(**context)
            .create(
                {
                    "advance_payment_method": "percentage",
                    "amount": 50,
                }
            )
        )
        payment_wizard.create_invoices()

        invoice_down_payment = sale_order_1.invoice_ids[0]
        invoice_down_payment.action_post()
        payment_register = Form(
            self.env["account.payment.register"].with_context(
                active_model="account.move",
                active_ids=invoice_down_payment.ids,
            )
        )
        journal_cash = self.env["account.journal"].search(
            [
                ("type", "=", "cash"),
                ("company_id", "=", invoice_down_payment.company_id.id),
            ],
            limit=1,
        )
        payment_register.journal_id = journal_cash
        payment_register.amount = invoice_down_payment.amount_total
        payment_register.save()._create_payments()

        picking = sale_order_1.picking_ids
        self.picking_move_state(picking)
        invoice = self.create_invoice_wizard(picking)
        # 2 Products, 2 Down Payment, 1 Note and 1 Section
        self.assertEqual(len(invoice.invoice_line_ids), 6)
        line_section = invoice.invoice_line_ids.filtered(
            lambda line: line.display_type == "line_section"
        )
        assert line_section, "Invoice without Line Section for Down Payment."
        down_payment_line = invoice.invoice_line_ids.filtered(
            lambda line: line.sale_line_ids.is_downpayment
        )
        assert down_payment_line, "Invoice without Down Payment line."

    def test_default_value_sale_invoicing_policy(self):
        """Test default value for sale_invoicing_policy"""
        company = self.env["res.company"].create(
            {
                "name": "Test",
            }
        )
        self.assertEqual(company.sale_invoicing_policy, "sale_order")

    def test_picking_invocing_without_sale_order(self):
        """Test Picking Invoicing without Sale Order"""
        picking = self.env.ref("stock_picking_invoicing.stock_picking_invoicing_1")
        self.picking_move_state(picking)
        invoice = self.create_invoice_wizard(picking)
        self.assertEqual(len(invoice), 1)
