# Copyright 2022 ForgeFlow S.L
# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html

from odoo.exceptions import UserError, ValidationError
from odoo.tests.common import TransactionCase


class TestStockInventory(TransactionCase):
    def setUp(self):
        super(TestStockInventory, self).setUp()
        self.env.company.stock_inventory_auto_complete = False
        self.quant_model = self.env["stock.quant"]
        self.move_model = self.env["stock.move.line"]
        self.inventory_model = self.env["stock.inventory"]
        self.location_model = self.env["stock.location"]
        self.product_categ = self.env["product.category"].create({"name": "Test Categ"})
        self.product = self.env["product.product"].create(
            {
                "name": "Product 1 test",
                "type": "product",
                "tracking": "lot",
            }
        )
        self.product2 = self.env["product.product"].create(
            {
                "name": "Product 1 test",
                "type": "product",
                "categ_id": self.product_categ.id,
            }
        )
        self.lot_1 = self.env["stock.lot"].create(
            {
                "product_id": self.product.id,
                "name": "Lot 1",
                "company_id": self.env.company.id,
            }
        )
        self.lot_2 = self.env["stock.lot"].create(
            {
                "product_id": self.product.id,
                "name": "Lot 2",
                "company_id": self.env.company.id,
            }
        )
        self.lot_3 = self.env["stock.lot"].create(
            {
                "product_id": self.product.id,
                "name": "Lot 3",
                "company_id": self.env.company.id,
            }
        )
        self.location_src = self.env.ref("stock.stock_location_locations_virtual")
        self.location_dst = self.env.ref("stock.stock_location_customers")

        self.location1 = self.location_model.create(
            {
                "name": "Location 1",
                "usage": "internal",
                "location_id": self.location_src.id,
            }
        )
        self.location2 = self.location_model.create(
            {
                "name": "Location 2",
                "usage": "internal",
                "location_id": self.location_dst.id,
            }
        )
        self.location3 = self.location_model.create(
            {
                "name": "Location 3",
                "usage": "internal",
                "location_id": self.location1.id,
            }
        )
        self.quant1 = self.quant_model.sudo().create(
            {
                "product_id": self.product.id,
                "lot_id": self.lot_1.id,
                "quantity": 100.0,
                "location_id": self.location1.id,
            }
        )
        self.quant2 = self.quant_model.sudo().create(
            {
                "product_id": self.product.id,
                "lot_id": self.lot_2.id,
                "quantity": 100.0,
                "location_id": self.location2.id,
            }
        )
        self.quant3 = self.quant_model.sudo().create(
            {
                "product_id": self.product.id,
                "lot_id": self.lot_3.id,
                "quantity": 100.0,
                "location_id": self.location3.id,
            }
        )
        self.quant4 = self.quant_model.sudo().create(
            {
                "product_id": self.product2.id,
                "quantity": 100.0,
                "location_id": self.location3.id,
            }
        )

    def test_01_all_locations(self):
        inventory1 = self.inventory_model.create(
            {
                "name": "Inventory_Test_1",
                "product_selection": "all",
                "location_ids": [self.location1.id],
            }
        )
        inventory1.action_state_to_in_progress()
        inventory2 = self.inventory_model.create(
            {
                "name": "Inventory_Test_2",
                "product_selection": "all",
                "location_ids": [self.location1.id],
            }
        )
        with self.assertRaises(UserError), self.cr.savepoint():
            inventory2.action_state_to_in_progress()
        self.assertEqual(inventory1.state, "in_progress")
        self.assertEqual(
            inventory1.stock_quant_ids.ids,
            [self.quant1.id, self.quant3.id, self.quant4.id],
        )
        inventory1.action_state_to_draft()
        self.assertEqual(inventory1.stock_quant_ids.ids, [])
        inventory1.action_state_to_in_progress()
        self.assertEqual(inventory1.count_stock_moves, 0)
        self.assertEqual(inventory1.count_stock_quants, 3)
        self.assertEqual(inventory1.count_stock_quants_string, "3 / 3")
        inventory1.action_view_inventory_adjustment()
        self.quant1.inventory_quantity = 92
        self.quant1.action_apply_inventory()
        inventory1.invalidate_recordset()
        inventory1.action_view_stock_moves()
        self.assertEqual(inventory1.count_stock_moves, 1)
        self.assertEqual(inventory1.count_stock_quants, 3)
        self.assertEqual(inventory1.count_stock_quants_string, "2 / 3")
        self.assertEqual(inventory1.stock_move_ids.qty_done, 8)
        self.assertEqual(inventory1.stock_move_ids.product_id.id, self.product.id)
        self.assertEqual(inventory1.stock_move_ids.lot_id.id, self.lot_1.id)
        self.assertEqual(inventory1.stock_move_ids.location_id.id, self.location1.id)
        inventory1.action_state_to_done()

    def test_02_manual_selection(self):
        inventory1 = self.inventory_model.create(
            {
                "name": "Inventory_Test_3",
                "product_selection": "manual",
                "location_ids": [self.location1.id],
                "product_ids": [self.product.id],
            }
        )
        inventory1.action_state_to_in_progress()
        self.assertEqual(inventory1.state, "in_progress")
        self.assertEqual(
            inventory1.stock_quant_ids.ids, [self.quant1.id, self.quant3.id]
        )
        inventory1.action_state_to_draft()
        self.assertEqual(inventory1.stock_quant_ids.ids, [])
        inventory1.action_state_to_in_progress()
        self.assertEqual(inventory1.state, "in_progress")
        self.assertEqual(inventory1.count_stock_moves, 0)
        self.assertEqual(inventory1.count_stock_quants, 2)
        self.assertEqual(inventory1.count_stock_quants_string, "2 / 2")
        inventory1.action_view_inventory_adjustment()
        self.quant3.inventory_quantity = 74
        self.quant3.action_apply_inventory()
        inventory1.invalidate_recordset()
        self.assertEqual(inventory1.count_stock_moves, 1)
        self.assertEqual(inventory1.count_stock_quants, 2)
        self.assertEqual(inventory1.count_stock_quants_string, "1 / 2")
        self.assertEqual(inventory1.stock_move_ids.qty_done, 26)
        self.assertEqual(inventory1.stock_move_ids.product_id.id, self.product.id)
        self.assertEqual(inventory1.stock_move_ids.lot_id.id, self.lot_3.id)
        self.assertEqual(inventory1.stock_move_ids.location_id.id, self.location3.id)
        self.quant1.inventory_quantity = 65
        self.quant1.action_apply_inventory()
        inventory1.invalidate_recordset()
        self.assertEqual(inventory1.count_stock_moves, 2)
        self.assertEqual(inventory1.count_stock_quants, 2)
        self.assertEqual(inventory1.count_stock_quants_string, "0 / 2")
        inventory1.action_state_to_done()

    def test_03_one_selection(self):
        with self.assertRaises(UserError), self.cr.savepoint():
            self.inventory_model.create(
                {
                    "name": "Inventory_Test_5",
                    "product_selection": "one",
                    "location_ids": [self.location1.id],
                    "product_ids": [self.product.id, self.product2.id],
                }
            )
        inventory1 = self.inventory_model.create(
            {
                "name": "Inventory_Test_5",
                "product_selection": "one",
                "location_ids": [self.location1.id],
                "product_ids": [self.product.id],
            }
        )
        inventory1.action_state_to_in_progress()
        inventory1.product_ids = [self.product.id]
        self.assertEqual(
            inventory1.stock_quant_ids.ids, [self.quant1.id, self.quant3.id]
        )
        inventory1.action_state_to_draft()
        self.assertEqual(inventory1.stock_quant_ids.ids, [])
        inventory1.action_state_to_in_progress()
        self.assertEqual(inventory1.state, "in_progress")
        self.assertEqual(inventory1.count_stock_moves, 0)
        self.assertEqual(inventory1.count_stock_quants, 2)
        self.assertEqual(inventory1.count_stock_quants_string, "2 / 2")
        inventory1.action_view_inventory_adjustment()
        self.quant3.inventory_quantity = 74
        self.quant3.action_apply_inventory()
        inventory1.invalidate_recordset()
        inventory1.action_view_stock_moves()
        self.assertEqual(inventory1.count_stock_moves, 1)
        self.assertEqual(inventory1.count_stock_quants, 2)
        self.assertEqual(inventory1.count_stock_quants_string, "1 / 2")
        self.assertEqual(inventory1.stock_move_ids.qty_done, 26)
        self.assertEqual(inventory1.stock_move_ids.product_id.id, self.product.id)
        self.assertEqual(inventory1.stock_move_ids.lot_id.id, self.lot_3.id)
        self.assertEqual(inventory1.stock_move_ids.location_id.id, self.location3.id)
        self.quant1.inventory_quantity = 65
        self.quant1.action_apply_inventory()
        inventory1.invalidate_recordset()
        self.assertEqual(inventory1.count_stock_moves, 2)
        self.assertEqual(inventory1.count_stock_quants, 2)
        self.assertEqual(inventory1.count_stock_quants_string, "0 / 2")
        inventory1.action_state_to_done()

    def test_04_lot_selection(self):
        with self.assertRaises(UserError), self.cr.savepoint():
            self.inventory_model.create(
                {
                    "name": "Inventory_Test_6",
                    "product_selection": "lot",
                    "location_ids": [self.location1.id],
                    "lot_ids": [self.lot_3.id],
                    "product_ids": [self.product.id, self.product2.id],
                }
            )
        inventory1 = self.inventory_model.create(
            {
                "name": "Inventory_Test_6",
                "product_selection": "lot",
                "location_ids": [self.location1.id],
                "lot_ids": [self.lot_3.id],
                "product_ids": [self.product.id],
            }
        )
        inventory1.product_ids = [self.product.id]
        inventory1.action_state_to_in_progress()
        self.assertEqual(inventory1.stock_quant_ids.ids, [self.quant3.id])
        inventory1.action_state_to_draft()
        self.assertEqual(inventory1.stock_quant_ids.ids, [])
        inventory1.action_state_to_in_progress()
        self.assertEqual(inventory1.state, "in_progress")
        self.assertEqual(inventory1.count_stock_moves, 0)
        self.assertEqual(inventory1.count_stock_quants, 1)
        self.assertEqual(inventory1.count_stock_quants_string, "1 / 1")
        inventory1.action_view_inventory_adjustment()
        self.quant3.inventory_quantity = 74
        self.quant3.action_apply_inventory()
        inventory1.invalidate_recordset()
        inventory1.action_view_stock_moves()
        self.assertEqual(inventory1.count_stock_moves, 1)
        self.assertEqual(inventory1.count_stock_quants, 1)
        self.assertEqual(inventory1.count_stock_quants_string, "0 / 1")
        self.assertEqual(inventory1.stock_move_ids.qty_done, 26)
        self.assertEqual(inventory1.stock_move_ids.product_id.id, self.product.id)
        self.assertEqual(inventory1.stock_move_ids.lot_id.id, self.lot_3.id)
        self.assertEqual(inventory1.stock_move_ids.location_id.id, self.location3.id)
        inventory1.action_state_to_done()

    def test_05_category_selection(self):
        inventory1 = self.inventory_model.create(
            {
                "name": "Inventory_Test_7",
                "product_selection": "category",
                "location_ids": [self.location3.id],
                "category_id": self.product_categ.id,
            }
        )
        inventory1.action_state_to_in_progress()
        # Remove company_id from stock-quant to test it works as expected
        inventory1.stock_quant_ids.write({"company_id": False})
        self.assertEqual(inventory1.stock_quant_ids.ids, [self.quant4.id])
        inventory1.action_state_to_draft()
        self.assertEqual(inventory1.stock_quant_ids.ids, [])
        inventory1.action_state_to_in_progress()
        self.assertEqual(inventory1.state, "in_progress")
        self.assertEqual(inventory1.count_stock_moves, 0)
        self.assertEqual(inventory1.count_stock_quants, 1)
        self.assertEqual(inventory1.count_stock_quants_string, "1 / 1")
        inventory1.action_view_inventory_adjustment()
        self.quant4.inventory_quantity = 74
        self.quant4.action_apply_inventory()
        inventory1.invalidate_recordset()
        inventory1.action_view_stock_moves()
        self.assertEqual(inventory1.count_stock_moves, 1)
        self.assertEqual(inventory1.count_stock_quants, 1)
        self.assertEqual(inventory1.count_stock_quants_string, "0 / 1")
        self.assertEqual(inventory1.stock_move_ids.qty_done, 26)
        self.assertEqual(inventory1.stock_move_ids.product_id.id, self.product2.id)
        self.assertEqual(inventory1.stock_move_ids.location_id.id, self.location3.id)
        inventory1.action_state_to_done()

    def test_06_exclude_sub_locations(self):
        inventory1 = self.inventory_model.create(
            {
                "name": "Inventory_Test_1",
                "product_selection": "all",
                "location_ids": [self.location1.id],
                "exclude_sublocation": True,
            }
        )
        inventory1.action_state_to_in_progress()
        inventory2 = self.inventory_model.create(
            {
                "name": "Inventory_Test_2",
                "product_selection": "all",
                "location_ids": [self.location1.id],
                "exclude_sublocation": True,
            }
        )
        with self.assertRaises(UserError), self.cr.savepoint():
            inventory2.action_state_to_in_progress()
        self.assertEqual(inventory1.state, "in_progress")
        self.assertEqual(
            inventory1.stock_quant_ids.ids,
            [self.quant1.id],
        )
        inventory1.action_state_to_draft()
        self.assertEqual(inventory1.stock_quant_ids.ids, [])
        inventory1.action_state_to_in_progress()
        self.assertEqual(inventory1.count_stock_moves, 0)
        self.assertEqual(inventory1.count_stock_quants, 1)
        self.assertEqual(inventory1.count_stock_quants_string, "1 / 1")
        inventory1.action_view_inventory_adjustment()
        self.quant1.inventory_quantity = 92
        self.quant1.action_apply_inventory()
        inventory1._compute_count_stock_quants()
        inventory1.action_view_stock_moves()
        self.assertEqual(inventory1.count_stock_moves, 1)
        self.assertEqual(inventory1.count_stock_quants, 1)
        self.assertEqual(inventory1.count_stock_quants_string, "0 / 1")
        self.assertEqual(inventory1.stock_move_ids.qty_done, 8)
        self.assertEqual(inventory1.stock_move_ids.product_id.id, self.product.id)
        self.assertEqual(inventory1.stock_move_ids.lot_id.id, self.lot_1.id)
        self.assertEqual(inventory1.stock_move_ids.location_id.id, self.location1.id)
        inventory1.action_state_to_done()

    def test_07_stock_inventory_auto_complete(self):
        self.env.company.stock_inventory_auto_complete = True
        with self.assertRaises(ValidationError), self.cr.savepoint():
            inventory1 = self.inventory_model.create(
                {
                    "name": "Inventory_Test_5",
                    "product_selection": "one",
                    "location_ids": [self.location1.id],
                    "product_ids": [self.product.id, self.product2.id],
                }
            )
        inventory1 = self.inventory_model.create(
            {
                "name": "Inventory_Test_5",
                "product_selection": "one",
                "location_ids": [self.location1.id],
                "product_ids": [self.product.id],
            }
        )
        inventory1.action_state_to_in_progress()
        inventory1.product_ids = [self.product.id]
        self.assertEqual(
            inventory1.stock_quant_ids.ids, [self.quant1.id, self.quant3.id]
        )
        inventory1.action_state_to_draft()
        self.assertEqual(inventory1.stock_quant_ids.ids, [])
        inventory1.action_state_to_in_progress()
        self.assertEqual(inventory1.state, "in_progress")
        self.assertEqual(inventory1.count_stock_moves, 0)
        self.assertEqual(inventory1.count_stock_quants, 2)
        self.assertEqual(inventory1.count_stock_quants_string, "2 / 2")
        inventory1.action_view_inventory_adjustment()
        self.quant3.inventory_quantity = 74
        self.quant3.action_apply_inventory()
        inventory1._compute_count_stock_quants()
        inventory1.action_view_stock_moves()
        self.assertEqual(inventory1.count_stock_moves, 1)
        self.assertEqual(inventory1.count_stock_quants, 2)
        self.assertEqual(inventory1.count_stock_quants_string, "1 / 2")
        self.assertEqual(inventory1.stock_move_ids.qty_done, 26)
        self.assertEqual(inventory1.stock_move_ids.product_id.id, self.product.id)
        self.assertEqual(inventory1.stock_move_ids.lot_id.id, self.lot_3.id)
        self.assertEqual(inventory1.stock_move_ids.location_id.id, self.location3.id)
        self.quant1.inventory_quantity = 65
        self.quant1.action_apply_inventory()
        self.assertEqual(inventory1.count_stock_moves, 2)
        self.assertEqual(inventory1.count_stock_quants, 2)
        self.assertEqual(inventory1.state, "done")

    def test_08_multiple_inventories_same_location(self):
        quant1 = self.quant_model.search(
            [
                ("product_id", "=", self.product.id),
                ("location_id", "=", self.location1.id),
            ]
        )
        self.assertTrue(quant1)
        self.quant_model.sudo().create(
            {
                "product_id": self.product2.id,
                "quantity": 100.0,
                "location_id": self.location1.id,
            }
        )
        quant2 = self.quant_model.search(
            [
                ("product_id", "=", self.product2.id),
                ("location_id", "=", self.location1.id),
            ]
        )
        self.assertTrue(quant2)
        inventory1 = self.inventory_model.create(
            {
                "name": "Inventory_Test_8_1",
                "product_selection": "manual",
                "location_ids": [self.location1.id],
                "product_ids": [self.product.id],
            }
        )
        inventory1.action_state_to_in_progress()
        self.assertEqual(inventory1.state, "in_progress")
        inventory2 = self.inventory_model.create(
            {
                "name": "Inventory_Test_8_2",
                "product_selection": "manual",
                "location_ids": [self.location1.id],
                "product_ids": [self.product2.id],
            }
        )
        inventory2.action_state_to_in_progress()
        self.assertEqual(inventory2.state, "in_progress")
        self.assertEqual(inventory1.state, "in_progress")

    def test_09_product_inventory_global_and_sublocations_review(self):
        self.location4 = self.location_model.create(
            {
                "name": "Location 4",
                "usage": "internal",
                "location_id": self.location1.id,
            }
        )
        location_global = self.location_model.create(
            {
                "name": "Global Location",
                "usage": "internal",
            }
        )
        self.location1.location_id = location_global.id
        self.location2.location_id = location_global.id
        self.location3.location_id = location_global.id
        self.location4.location_id = location_global.id
        inventory_global = self.inventory_model.create(
            {
                "name": "Inventory_Global",
                "product_selection": "manual",
                "location_ids": [location_global.id],
                "product_ids": [self.product.id],
            }
        )
        inventory_global.action_state_to_in_progress()
        self.assertEqual(inventory_global.state, "in_progress")
        inventory_sub_no_product = self.inventory_model.create(
            {
                "name": "Inventory_Sub_No_Product",
                "product_selection": "manual",
                "location_ids": [self.location4.id],
            }
        )
        inventory_sub_no_product.action_state_to_in_progress()
        self.assertEqual(inventory_sub_no_product.state, "in_progress")
        with self.assertRaises(ValidationError), self.cr.savepoint():
            inventory_sub_with_product = self.inventory_model.create(
                {
                    "name": "Inventory_Sub_With_Product",
                    "product_selection": "manual",
                    "location_ids": [self.location1.id],
                }
            )
            inventory_sub_with_product.action_state_to_in_progress()

    def test_10_inventory_quant_to_do_states(self):
        inventory = self.inventory_model.create(
            {
                "name": "Inventory_Test_10",
                "product_selection": "manual",
                "location_ids": [self.location1.id],
                "product_ids": [self.product.id],
            }
        )
        inventory.action_state_to_in_progress()
        quants = inventory.stock_quant_ids
        self.assertTrue(all(quant.to_do for quant in quants))
        inventory.action_state_to_draft()
        self.assertFalse(inventory.stock_quant_ids)
        self.assertTrue(all(not quant.to_do for quant in quants))
        inventory.action_state_to_in_progress()
        quants = inventory.stock_quant_ids
        self.assertTrue(all(quant.to_do for quant in quants))
        self.assertTrue(inventory.stock_quant_ids)
        inventory.action_state_to_done()
        self.assertTrue(all(not quant.to_do for quant in quants))

    def test_11_products_under_review(self):
        inventory1 = self.inventory_model.create(
            {
                "name": "Inventory_Test_Under_Review_1",
                "product_selection": "all",
                "location_ids": [self.location1.id],
            }
        )
        inventory1.action_state_to_in_progress()
        self.assertEqual(inventory1.state, "in_progress")
        self.assertIn(self.product, inventory1.products_under_review_ids)
        inventory1.action_state_to_done()
        self.assertEqual(inventory1.state, "done")
        self.assertEqual(inventory1.products_under_review_ids.ids, [])

    def test_12_search_products_under_review(self):
        inventory = self.inventory_model.create(
            {
                "name": "Inventory for Search Test",
                "product_selection": "all",
                "location_ids": [self.location1.id],
            }
        )
        inventory.action_state_to_in_progress()
        search_result = self.inventory_model._search_products_under_review_ids(
            "=", self.product.id
        )
        expected_result = [("id", "in", [inventory.id]), ("state", "=", "in_progress")]
        self.assertEqual(
            search_result,
            expected_result,
            "The search function did not return the expected results",
        )

    def test_13_multiple_inventories_different_products_same_location(self):
        inventory1 = self.inventory_model.create(
            {
                "name": "Inventory1 for Product1",
                "product_ids": [(6, 0, [self.product.id])],
                "location_ids": [(6, 0, [self.location3.id])],
                "product_selection": "manual",
            }
        )
        inventory2 = self.inventory_model.create(
            {
                "name": "Inventory2 for Product2",
                "product_ids": [(6, 0, [self.product2.id])],
                "location_ids": [(6, 0, [self.location3.id])],
                "product_selection": "manual",
            }
        )
        inventory1.action_state_to_in_progress()
        inventory2.action_state_to_in_progress()
        self.assertEqual(inventory1.state, "in_progress")
        self.assertEqual(inventory2.state, "in_progress")
        self.assertEqual(
            inventory1.stock_quant_ids.filtered(
                lambda q: q.product_id == self.product
            ).current_inventory_id,
            inventory1,
        )
        self.assertEqual(
            inventory2.stock_quant_ids.filtered(
                lambda q: q.product_id == self.product2
            ).current_inventory_id,
            inventory2,
        )
