# Copyright 2021 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from odoo import fields

from .test_delivery_shipment_base import DeliveryShipmentCommonCase


class DeliveryShipmentScanDocumentPackageCase(DeliveryShipmentCommonCase):
    """Tests for '/scan_document' endpoint when scanning a package."""

    def test_scan_document_shipment_planned_package_not_planned(self):
        """Scan a package not planned in the shipment advice.

        The shipment advice has some content planned but the user scans an
        unrelated one, returning an error.
        """
        self._plan_records_in_shipment(self.shipment, self.picking1.move_lines)
        scanned_package = self.picking2.package_level_ids.package_id
        response = self.service.dispatch(
            "scan_document",
            params={
                "shipment_advice_id": self.shipment.id,
                "barcode": scanned_package.name,
            },
        )
        self.assert_response_scan_document(
            response,
            self.shipment,
            message=self.service.msg_store.package_not_planned_in_shipment(
                scanned_package, self.shipment
            ),
        )

    def test_scan_document_shipment_planned_package_planned(self):
        """Scan a package planned in the shipment advice.

        The shipment advice has some content planned and the user scans an
        expected one, loading the package and returning the loading list of the
        shipment as it is now fully loaded.
        """
        package_level = self.picking1.package_level_ids
        self._plan_records_in_shipment(
            self.shipment, package_level.move_line_ids.move_id
        )
        scanned_package = package_level.package_id
        response = self.service.dispatch(
            "scan_document",
            params={
                "shipment_advice_id": self.shipment.id,
                "barcode": scanned_package.name,
            },
        )
        self.assert_response_loading_list(
            response,
            self.shipment,
            message=self.service.msg_store.shipment_planned_content_fully_loaded(),
        )
        # Check package level status
        self.assertTrue(package_level.is_done)
        # Check returned content
        lading = response["data"]["loading_list"]["lading"]
        on_dock = response["data"]["loading_list"]["on_dock"]
        #   'lading' key contains the related delivery
        self.assertEqual(lading, self.service.data.pickings_loaded(self.picking1))
        #   'on_dock' key is empty as there is no other delivery planned
        self.assertFalse(on_dock)

    def test_scan_document_shipment_not_planned_package_not_planned(self):
        """Scan a package not planned for a shipment not planned.

        Load the package and return the available content to load/unload
        of the related delivery.
        """
        package_level = self.picking1.package_level_ids
        scanned_package = package_level.package_id
        response = self.service.dispatch(
            "scan_document",
            params={
                "shipment_advice_id": self.shipment.id,
                "barcode": scanned_package.name,
            },
        )
        self.assert_response_scan_document(response, self.shipment, self.picking1)
        # Check package level status
        self.assertTrue(package_level.is_done)
        # Check returned content
        location_src = self.picking_type.default_location_src_id.name
        content = response["data"]["scan_document"]["content"]
        self.assertIn(location_src, content)
        #   'move_lines' key contains the lines available from the same delivery
        self.assertEqual(
            content[location_src]["move_lines"],
            self.service.data.move_lines(self.picking1.move_line_ids_without_package),
        )
        #   'package_levels' key contains the package which has been loaded
        self.assertEqual(
            content[location_src]["package_levels"],
            self.service.data.package_levels(package_level),
        )

    def test_scan_document_package_already_loaded(self):
        """Scan a package already loaded in the current shipment.

        The second time a package is scanned an warning is returned saying that
        the package has already been loaded.
        """
        package_level = self.picking1.package_level_ids
        scanned_package = package_level.package_id
        # First scan
        self.service.dispatch(
            "scan_document",
            params={
                "shipment_advice_id": self.shipment.id,
                "barcode": scanned_package.name,
            },
        )
        # Second scan
        response = self.service.dispatch(
            "scan_document",
            params={
                "shipment_advice_id": self.shipment.id,
                "barcode": scanned_package.name,
            },
        )
        self.assert_response_scan_document(
            response,
            self.shipment,
            self.picking1,
            message=self.service.msg_store.package_already_loaded_in_shipment(
                scanned_package,
                self.shipment,
            ),
        )
        # Check package level status
        self.assertTrue(package_level.is_done)
        # Check returned content
        location_src = self.picking_type.default_location_src_id.name
        content = response["data"]["scan_document"]["content"]
        self.assertIn(location_src, content)
        #   'move_lines' key contains the only one product without package
        self.assertEqual(
            content[location_src]["move_lines"],
            self.service.data.move_lines(self.picking1.move_line_ids_without_package),
        )
        #   'package_levels' key contains the package which has been loaded
        self.assertEqual(
            content[location_src]["package_levels"],
            self.service.data.package_levels(package_level),
        )

    @classmethod
    def _create_picking_chain(cls, wh, products):
        if products is None:
            products = []
        group = cls.env["procurement.group"].create(
            {
                "name": "TEST",
                "move_type": "direct",
                "partner_id": cls.customer.id,
            }
        )
        values = {
            "company_id": wh.company_id,
            "group_id": group,
            "date_planned": fields.Datetime.now(),
            "warehouse_id": wh,
        }
        for product, qty in products:
            uom = product.uom_id
            cls.env["procurement.group"].run(
                [
                    cls.env["procurement.group"].Procurement(
                        product,
                        qty,
                        uom,
                        cls.loc_customers,
                        "TEST",
                        "TEST",
                        wh.company_id,
                        values,
                    )
                ]
            )
        return cls.env["stock.picking"].search([("group_id", "=", group.id)])

    def test_scan_document_package_partially_reserved(self):
        wh = self.wh
        wh.sudo().delivery_steps = "pick_ship"
        pickings = self._create_picking_chain(
            wh, [(self.product_a, 10.0), (self.product_b, 10.0)]
        )
        out_picking = pickings.filtered(lambda p: p.picking_type_id.code == "outgoing")
        pick_picking = pickings.filtered(lambda p: p.picking_type_id.code == "internal")
        package = self.env["stock.quant.package"].create({})
        self._fill_stock_for_moves(pick_picking.move_lines, in_package=package)
        pick_picking.action_assign()
        for move in pick_picking.move_lines:
            move.quantity_done = move.product_uom_qty
            move.move_line_ids.qty_done = move.product_uom_qty
        pick_picking._action_done()
        # create shipment
        shipment = self._create_shipment()
        # Create return for product a
        move_a = pick_picking.move_lines.filtered(
            lambda m: m.product_id == self.product_a
        )
        wiz_vals = {
            "picking_id": pick_picking.id,
            "location_id": pick_picking.location_id.id,
            "product_return_moves": [
                (
                    0,
                    0,
                    {
                        "product_id": move_a.product_id.id,
                        "quantity": move_a.quantity_done,
                        "uom_id": move_a.product_uom.id,
                        "move_id": move_a.id,
                    },
                )
            ],
        }
        wiz = self.env["stock.return.picking"].create(wiz_vals)
        res = wiz.create_returns()
        return_picking = self.env["stock.picking"].browse(res["res_id"])
        # Try to ship the goods, it should be prevented
        response = self.service.dispatch(
            "scan_document",
            params={
                "shipment_advice_id": shipment.id,
                "barcode": package.name,
            },
        )
        self.assert_response_scan_document(
            response,
            shipment,
            message=self.service.msg_store.package_partially_reserved_in_picking(
                return_picking | out_picking
            ),
        )

    def test_scan_document_shipment_not_planned_package_planned(self):
        """Scan an already planned package in the shipment not planned.

        Returns an error saying that the package could not be loaded.
        """
        package_level = self.picking1.package_level_ids
        scanned_package = package_level.package_id
        # Plan the package in a another shipment
        new_shipment = self._create_shipment()
        self._plan_records_in_shipment(
            new_shipment, package_level.move_line_ids.move_id
        )
        # Scan the package: an error is returned as this package has already
        # been planned in another shipment
        response = self.service.dispatch(
            "scan_document",
            params={
                "shipment_advice_id": self.shipment.id,
                "barcode": scanned_package.name,
            },
        )
        self.assert_response_scan_document(
            response,
            self.shipment,
            message=self.service.msg_store.unable_to_load_package_in_shipment(
                scanned_package, self.shipment
            ),
        )
