# Copyright 2020 Camptocamp SA (http://www.camptocamp.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from itertools import product

from .test_checkout_base import CheckoutCommonCase
from .test_checkout_select_package_base import CheckoutSelectPackageMixin


class CheckoutScanPackageActionCase(CheckoutCommonCase, CheckoutSelectPackageMixin):
    def _test_select_product(
        self, barcode_func, origin_qty_func, expected_qty_func, in_lot=False
    ):
        picking = self._create_picking(
            lines=[(self.product_a, 10), (self.product_b, 10), (self.product_c, 10)]
        )
        for move_line in picking.move_lines:
            # put in 3 different packages
            self._fill_stock_for_moves(move_line, in_package=True, in_lot=in_lot)
        picking.action_assign()

        # we have selected the pack that contains product a
        line_a = picking.move_line_ids[0]
        line_a.qty_done = origin_qty_func(line_a)

        response = self.service.dispatch(
            "scan_package_action",
            params={
                "picking_id": picking.id,
                "selected_line_ids": line_a.ids,
                "barcode": barcode_func(line_a),
            },
        )

        # since we scanned the barcode of the product and we had a
        # qty_done, the qty_done should flip to 0
        self._assert_selected_qties(
            response, line_a, {line_a: expected_qty_func(line_a)}
        )

    def test_scan_package_action_select_product(self):
        self._test_select_product(
            lambda l: l.product_id.barcode, lambda l: l.product_uom_qty, lambda __: 0
        )

    def test_scan_package_action_deselect_product(self):
        self._test_select_product(
            lambda l: l.product_id.barcode, lambda __: 0, lambda l: l.product_uom_qty
        )

    def test_scan_package_action_select_product_packaging(self):
        self._test_select_product(
            lambda l: l.product_id.packaging_ids.barcode,
            lambda l: l.product_uom_qty,
            lambda __: 0,
        )

    def test_scan_package_action_deselect_product_packaging(self):
        self._test_select_product(
            lambda l: l.product_id.packaging_ids.barcode,
            lambda __: 0,
            lambda l: l.product_uom_qty,
        )

    def test_scan_package_action_select_product_lot(self):
        self._test_select_product(
            lambda l: l.lot_id.name,
            lambda __: 0,
            lambda l: l.product_uom_qty,
            in_lot=True,
        )

    def test_scan_package_action_deselect_product_lot(self):
        self._test_select_product(
            lambda l: l.lot_id.name,
            lambda l: l.product_uom_qty,
            lambda __: 0,
            in_lot=True,
        )

    def _test_scan_package_action_scan_product_error_tracked_by(
        self, tracked_by, barcode
    ):
        self.product_a.tracking = tracked_by
        picking = self._create_picking(lines=[(self.product_a, 1)])
        self._fill_stock_for_moves(picking.move_lines, in_package=True)
        picking.action_assign()
        move_line = picking.move_line_ids
        origin_qty_done = move_line.qty_done
        response = self.service.dispatch(
            "scan_package_action",
            params={
                "picking_id": picking.id,
                "selected_line_ids": move_line.ids,
                "barcode": barcode,
            },
        )
        self._assert_selected_qties(
            response,
            move_line,
            # no change as the scan was not valid
            {move_line: origin_qty_done},
            message={
                "message_type": "warning",
                "body": "Product tracked by lot, please scan one.",
            },
        )

    def test_scan_package_action_scan_product_error_tracking(self):
        trackings = ("lot", "serial")
        barcodes = (self.product_a.barcode, self.product_a.packaging_ids.barcode)
        for tracking, barcode in product(trackings, barcodes):
            self._test_scan_package_action_scan_product_error_tracked_by(
                tracking, barcode
            )

    def test_scan_package_action_scan_package_keep_source_package_ok(self):
        picking = self._create_picking(
            lines=[
                (self.product_a, 10),
                (self.product_b, 10),
                (self.product_c, 10),
                (self.product_d, 10),
            ]
        )
        pack1_moves = picking.move_lines[:3]
        pack2_moves = picking.move_lines[3:]
        # put in 2 packs, for this test, we'll work on pack1
        self._fill_stock_for_moves(pack1_moves, in_package=True)
        self._fill_stock_for_moves(pack2_moves, in_package=True)
        picking.action_assign()

        selected_lines = pack1_moves.move_line_ids
        pack1 = pack1_moves.move_line_ids.package_id

        move_line1, move_line2, move_line3 = selected_lines
        # We'll put only product A and B in the package
        move_line1.qty_done = move_line1.product_uom_qty
        move_line2.qty_done = move_line2.product_uom_qty
        move_line3.qty_done = 0

        response = self.service.dispatch(
            "scan_package_action",
            params={
                "picking_id": picking.id,
                "selected_line_ids": selected_lines.ids,
                # we keep the goods in the same package, so we scan the source package
                "barcode": pack1.name,
            },
        )

        self.assertRecordValues(
            move_line1,
            [{"result_package_id": pack1.id, "shopfloor_checkout_done": True}],
        )
        self.assertRecordValues(
            move_line2,
            [{"result_package_id": pack1.id, "shopfloor_checkout_done": True}],
        )
        self.assertRecordValues(
            move_line3,
            # qty_done was zero so we don't set it as packed
            [{"result_package_id": pack1.id, "shopfloor_checkout_done": False}],
        )
        self.assert_response(
            response,
            # go pack to the screen to select lines to put in packages
            next_state="select_line",
            data={"picking": self._stock_picking_data(picking)},
            message={
                "message_type": "success",
                "body": "Product(s) packed in {}".format(pack1.name),
            },
        )

    def test_scan_package_action_scan_package_error_invalid(self):
        picking = self._create_picking(lines=[(self.product_a, 10)])
        move = picking.move_lines
        self._fill_stock_for_moves(move, in_package=True)
        picking.action_assign()

        selected_line = move.move_line_ids
        other_package = self.env["stock.quant.package"].create({})

        response = self.service.dispatch(
            "scan_package_action",
            params={
                "picking_id": picking.id,
                "selected_line_ids": selected_line.ids,
                "barcode": other_package.name,
            },
        )

        self.assertRecordValues(
            selected_line,
            [
                {
                    # the result package must remain identical, so equal to the
                    # source package
                    "result_package_id": selected_line.package_id.id,
                    "shopfloor_checkout_done": False,
                }
            ],
        )
        self._assert_selected_response(
            response,
            selected_line,
            message={
                "message_type": "error",
                "body": "Not a valid destination package",
            },
        )

    def test_scan_package_action_scan_package_use_existing_package_ok(self):
        picking = self._create_picking(
            lines=[
                (self.product_a, 10),
                (self.product_b, 10),
                (self.product_c, 10),
                (self.product_d, 10),
            ]
        )
        pack1_moves = picking.move_lines[:3]
        pack2_moves = picking.move_lines[3:]
        # put in 2 packs, for this test, we'll work on pack1
        self._fill_stock_for_moves(pack1_moves, in_package=True)
        self._fill_stock_for_moves(pack2_moves, in_package=True)
        picking.action_assign()

        package = self.env["stock.quant.package"].create({})

        # assume that product d was already put in a package,
        # we must be able to put the lines of pack1 inside the same
        pack2_moves.move_line_ids.write(
            {"result_package_id": package.id, "shopfloor_checkout_done": True}
        )

        selected_lines = pack1_moves.move_line_ids
        # they are all selected
        selected_lines.write({"qty_done": 10.0})

        response = self.service.dispatch(
            "scan_package_action",
            params={
                "picking_id": picking.id,
                "selected_line_ids": selected_lines.ids,
                # use the package that was used for product D
                "barcode": package.name,
            },
        )

        self.assertRecordValues(
            selected_lines,
            [
                {"result_package_id": package.id, "shopfloor_checkout_done": True},
                {"result_package_id": package.id, "shopfloor_checkout_done": True},
                {"result_package_id": package.id, "shopfloor_checkout_done": True},
            ],
        )

        self.assert_response(
            response,
            # all the lines are packed, so we expect to go the summary screen
            next_state="summary",
            data={
                "picking": self._stock_picking_data(picking, done=True),
                "all_processed": True,
            },
            message={
                "message_type": "success",
                "body": "Product(s) packed in {}".format(package.name),
            },
        )

    def test_scan_package_action_scan_packaging_ok(self):
        picking = self._create_picking(
            lines=[
                (self.product_a, 10),
                (self.product_b, 10),
                (self.product_c, 10),
                (self.product_d, 10),
            ]
        )
        pack1_moves = picking.move_lines[:3]
        pack2_moves = picking.move_lines[3:]
        # put in 2 packs, for this test, we'll work on pack1
        self._fill_stock_for_moves(pack1_moves, in_package=True)
        self._fill_stock_for_moves(pack2_moves, in_package=True)
        picking.action_assign()

        selected_lines = pack1_moves.move_line_ids
        pack1 = pack1_moves.move_line_ids.package_id

        move_line1, move_line2, move_line3 = selected_lines
        # we'll put only the first 2 lines (product A and B) in the new package
        move_line1.qty_done = move_line1.product_uom_qty
        move_line2.qty_done = move_line2.product_uom_qty
        move_line3.qty_done = 0

        packaging = (
            self.env["product.packaging"]
            .sudo()
            .create(
                {
                    "name": "Pallet",
                    "barcode": "PPP",
                    "height": 12,
                    "width": 13,
                    "lngth": 14,
                }
            )
        )

        response = self.service.dispatch(
            "scan_package_action",
            params={
                "picking_id": picking.id,
                "selected_line_ids": selected_lines.ids,
                # create a new package using this packaging
                "barcode": packaging.barcode,
            },
        )

        new_package = move_line1.result_package_id
        self.assertNotEqual(pack1, new_package)

        self.assertRecordValues(
            new_package,
            [
                {
                    "packaging_id": packaging.id,
                    "lngth": packaging.lngth,
                    "width": packaging.width,
                    "height": packaging.height,
                }
            ],
        )

        self.assertRecordValues(
            move_line1,
            [{"result_package_id": new_package.id, "shopfloor_checkout_done": True}],
        )
        self.assertRecordValues(
            move_line2,
            [{"result_package_id": new_package.id, "shopfloor_checkout_done": True}],
        )
        self.assertRecordValues(
            move_line3,
            # qty_done was zero so we don't set it as packed and it remains in
            # the same package
            [{"result_package_id": pack1.id, "shopfloor_checkout_done": False}],
        )
        self.assert_response(
            response,
            # go pack to the screen to select lines to put in packages
            next_state="select_line",
            data={"picking": self._stock_picking_data(picking)},
            message={
                "message_type": "success",
                "body": "Product(s) packed in {}".format(new_package.name),
            },
        )

    def test_scan_package_action_scan_not_found(self):
        picking = self._create_picking(lines=[(self.product_a, 10)])
        move = picking.move_lines
        self._fill_stock_for_moves(move, in_package=True)
        picking.action_assign()
        selected_line = move.move_line_ids
        response = self.service.dispatch(
            "scan_package_action",
            params={
                "picking_id": picking.id,
                "selected_line_ids": selected_line.ids,
                # create a new package using this packaging
                "barcode": "BARCODE NOT FOUND",
            },
        )
        self._assert_selected_response(
            response,
            selected_line,
            message={"message_type": "error", "body": "Barcode not found"},
        )
