# Copyright 2016-20 ForgeFlow S.L. (http://www.forgeflow.com)
# Copyright 2016 Aleph Objects, Inc. (https://www.alephobjects.com/)
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html).

from datetime import datetime

import odoo.tests.common as common


class TestDdmrpCommon(common.TransactionCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()

        cls.env = cls.env(
            context=dict(
                cls.env.context,
                tracking_disable=True,
                # compatibility with ddmrp_cron_actions_as_job,
                # that would delay calls to "cron_actions" in these tests
                test_queue_job_no_delay=True,
            )
        )

        # Models
        cls.productModel = cls.env["product.product"]
        cls.templateModel = cls.env["product.template"]
        cls.bomModel = cls.env["mrp.bom"]
        cls.bomlineModel = cls.env["mrp.bom.line"]
        cls.bufferModel = cls.env["stock.buffer"]
        cls.pickingModel = cls.env["stock.picking"]
        cls.moveModel = cls.env["stock.move"]
        cls.quantModel = cls.env["stock.quant"]
        cls.estimateModel = cls.env["stock.demand.estimate"]
        cls.aducalcmethodModel = cls.env["product.adu.calculation.method"]
        cls.locationModel = cls.env["stock.location"]
        cls.make_procurement_wiz = cls.env["make.procurement.buffer"]
        cls.user_model = cls.env["res.users"]
        cls.partner_model = cls.env["res.partner"]
        cls.supinfo_model = cls.env["product.supplierinfo"]
        cls.pol_model = cls.env["purchase.order.line"]
        cls.wh_model = cls.env["stock.warehouse"]
        cls.orderpoint_model = cls.env["stock.warehouse.orderpoint"]

        # Refs
        cls.main_company = cls.env.ref("base.main_company")
        cls.second_company = cls.env.ref("stock.res_company_1")
        cls.warehouse = cls.env.ref("stock.warehouse0")
        cls.warehouse2 = cls.wh_model.create(
            {
                "partner_id": cls.env.ref("base.main_partner").id,
                "name": "Warehouse 2",
                "code": "WH2",
            }
        )
        cls.warehouse_sc = cls.env.ref("stock.stock_warehouse_shop0")
        cls.stock_location = cls.env.ref("stock.stock_location_stock")
        cls.stock_location_sc = cls.warehouse_sc.lot_stock_id
        cls.location_shelf1 = cls.env.ref("stock.stock_location_components")
        cls.supplier_location = cls.env.ref("stock.stock_location_suppliers")
        cls.customer_location = cls.env.ref("stock.stock_location_customers")
        cls.inter_wh = cls.env.ref("stock.stock_location_inter_wh")
        cls.inventory_location = cls.env["stock.location"].search(
            [("usage", "=", "inventory"), ("company_id", "=", cls.main_company.id)],
            limit=1,
        )
        cls.picking_type_out = cls.env.ref("stock.picking_type_out").copy(
            {
                "reservation_method": "manual",
                "sequence_code": "DDMRP-OUT",
            }
        )
        cls.picking_type_in = cls.env.ref("stock.picking_type_in")
        cls.picking_type_internal = cls.env.ref("stock.picking_type_internal").copy(
            {
                "reservation_method": "manual",
                "sequence_code": "DDMRP-INTERNAL",
            }
        )
        cls.uom_unit = cls.env.ref("uom.product_uom_unit")
        cls.dozen_unit = cls.env.ref("uom.product_uom_dozen")
        cls.buffer_profile_pur = cls.env.ref(
            "ddmrp.stock_buffer_profile_replenish_purchased_medium_medium"
        )
        cls.buffer_profile_mmm = cls.env.ref(
            "ddmrp.stock_buffer_profile_replenish_manufactured_medium_medium"
        )
        cls.buffer_profile_distr = cls.env.ref(
            "ddmrp.stock_buffer_profile_replenish_distributed_medium_medium"
        )
        cls.buffer_profile_override = cls.env.ref(
            "ddmrp.stock_buffer_profile_replenish_override_purchased_short_low"
        )
        cls.adu_fixed = cls.env.ref("ddmrp.adu_calculation_method_fixed")
        cls.group_stock_manager = cls.env.ref("stock.group_stock_manager")
        cls.group_mrp_user = cls.env.ref("mrp.group_mrp_user")
        cls.group_change_procure_qty = cls.env.ref(
            "ddmrp.group_change_buffer_procure_qty"
        )
        cls.group_buffer_manager = cls.env.ref("ddmrp.group_stock_buffer_maintainer")
        cls.calendar = cls.env.ref("resource.resource_calendar_std")
        cls.warehouse.calendar_id = cls.calendar

        # Create users
        cls.user = cls._create_user(
            "user_1",
            [
                cls.group_stock_manager,
                cls.group_mrp_user,
                cls.group_change_procure_qty,
                cls.group_buffer_manager,
            ],
        )
        # Create Partners:
        vendor = cls.partner_model.create({"name": "Test Vendor 1"})

        # Create products and BoM's:
        manufacture_route = cls.env.ref("mrp.route_warehouse0_manufacture")
        cls.productA = cls.productModel.create(
            {
                "name": "product A",
                "standard_price": 1,
                "type": "product",
                "uom_id": cls.uom_unit.id,
                "default_code": "A",
                "route_ids": [(6, 0, manufacture_route.ids)],
                "produce_delay": 10.0,
            }
        )
        cls.component_a1 = cls.productModel.create(
            {
                "name": "Component A-1",
                "standard_price": 1,
                "type": "product",
                "uom_id": cls.uom_unit.id,
                "default_code": "RM-test01",
            }
        )
        cls.bom_a = cls.bomModel.create(
            {
                "product_tmpl_id": cls.productA.product_tmpl_id.id,
                "product_id": cls.productA.id,
            }
        )
        cls.bomlineModel.create(
            {
                "product_id": cls.component_a1.id,
                "product_qty": 2.0,
                "bom_id": cls.bom_a.id,
            }
        )
        # Create locations:
        cls.binA = cls.locationModel.create(
            {
                "usage": "internal",
                "name": "Bin A",
                "location_id": cls.location_shelf1.id,
                "company_id": cls.main_company.id,
            }
        )

        cls.binB = cls.locationModel.create(
            {
                "usage": "internal",
                "name": "Bin B",
                "location_id": cls.location_shelf1.id,
                "company_id": cls.main_company.id,
            }
        )
        cls.locationModel._parent_store_compute()

        cls.quant = cls.quantModel.create(
            {
                "location_id": cls.binA.id,
                "company_id": cls.main_company.id,
                "product_id": cls.productA.id,
                "inventory_quantity": 200.0,
            }
        )
        cls.quant.action_apply_inventory()

        # Product B (purchased):
        buy_route = cls.env.ref("purchase_stock.route_warehouse0_buy")
        cls.product_purchased = cls.productModel.create(
            {
                "name": "product Purchased",
                "standard_price": 1,
                "type": "product",
                "uom_id": cls.uom_unit.id,
                "default_code": "B",
                "route_ids": [(6, 0, buy_route.ids)],
            }
        )
        cls.product_purchased_2 = cls.productModel.create(
            {
                "name": "product Purchased 2",
                "standard_price": 1,
                "type": "product",
                "uom_id": cls.uom_unit.id,
                "default_code": "B",
                "route_ids": [(6, 0, buy_route.ids)],
            }
        )
        cls.supinfo_model.create(
            {
                "product_tmpl_id": cls.product_purchased.product_tmpl_id.id,
                "partner_id": vendor.id,
                "delay": 20.0,
            }
        )

        # Product C (purchased and with variants):
        cls.template_c = cls.templateModel.create(
            {
                "name": "Product C",
                "type": "product",
                "uom_id": cls.uom_unit.id,
                "default_code": "C",
                "route_ids": [(6, 0, buy_route.ids)],
            }
        )
        cls.color_attribute = cls.env["product.attribute"].create(
            {"name": "Color", "sequence": 1}
        )
        cls.color_blue = cls.env["product.attribute.value"].create(
            {"name": "Blue", "attribute_id": cls.color_attribute.id, "sequence": 1}
        )
        cls.color_orange = cls.env["product.attribute.value"].create(
            {"name": "Orange", "attribute_id": cls.color_attribute.id, "sequence": 2}
        )
        cls.color_green = cls.env["product.attribute.value"].create(
            {"name": "Green", "attribute_id": cls.color_attribute.id, "sequence": 3}
        )
        cls.p_c_color_attribute_line = cls.env[
            "product.template.attribute.line"
        ].create(
            {
                "product_tmpl_id": cls.template_c.id,
                "attribute_id": cls.color_attribute.id,
                "value_ids": [
                    (6, 0, [cls.color_blue.id, cls.color_orange.id, cls.color_green.id])
                ],
            }
        )
        cls.product_c_blue = cls.template_c.product_variant_ids[0]
        cls.product_c_orange = cls.template_c.product_variant_ids[1]
        cls.product_c_green = cls.template_c.product_variant_ids[2]
        cls.p_c_supinfo_blue = cls.supinfo_model.create(
            {
                "product_tmpl_id": cls.template_c.id,
                "product_id": cls.product_c_blue.id,
                "partner_id": vendor.id,
                "delay": 5.0,
            }
        )
        cls.p_c_supinfo_orange = cls.supinfo_model.create(
            {
                "product_tmpl_id": cls.template_c.id,
                "product_id": cls.product_c_orange.id,
                "partner_id": vendor.id,
                "delay": 10.0,
            }
        )
        cls.p_c_supinfo_no_variant = cls.supinfo_model.create(
            {
                "product_tmpl_id": cls.template_c.id,
                "partner_id": vendor.id,
                "delay": 8.0,
            }
        )
        # Product D (distributed):
        cls.product_distributed = cls.productModel.create(
            {
                "name": "product Distributed",
                "standard_price": 1,
                "type": "product",
                "uom_id": cls.uom_unit.id,
                "default_code": "D",
                # TODO: "route_ids": [(6, 0, distribution_route.id)],
            }
        )

        # Create buffers:
        cls.buffer_a = cls.bufferModel.create(
            {
                "buffer_profile_id": cls.buffer_profile_mmm.id,
                "product_id": cls.productA.id,
                "location_id": cls.stock_location.id,
                "warehouse_id": cls.warehouse.id,
                "qty_multiple": 1.0,
                "adu_calculation_method": cls.adu_fixed.id,
                "adu_fixed": 4.0,
                "lead_days": 10.0,
                "order_spike_horizon": 10.0,
            }
        )
        cls.buffer_purchase = cls.bufferModel.create(
            {
                "buffer_profile_id": cls.buffer_profile_pur.id,
                "product_id": cls.product_purchased.id,
                "location_id": cls.stock_location.id,
                "warehouse_id": cls.warehouse.id,
                "qty_multiple": 1.0,
                "adu_calculation_method": cls.adu_fixed.id,
                "adu_fixed": 5.0,
            }
        )
        cls.buffer_c_blue = cls.bufferModel.create(
            {
                "buffer_profile_id": cls.buffer_profile_pur.id,
                "product_id": cls.product_c_blue.id,
                "location_id": cls.stock_location.id,
                "warehouse_id": cls.warehouse.id,
                "qty_multiple": 1.0,
                "adu_calculation_method": cls.adu_fixed.id,
                "adu_fixed": 5.0,
            }
        )
        cls.buffer_c_orange = cls.bufferModel.create(
            {
                "buffer_profile_id": cls.buffer_profile_pur.id,
                "product_id": cls.product_c_orange.id,
                "location_id": cls.stock_location.id,
                "warehouse_id": cls.warehouse.id,
                "qty_multiple": 1.0,
                "adu_calculation_method": cls.adu_fixed.id,
                "adu_fixed": 5.0,
            }
        )
        cls.buffer_distributed = cls.bufferModel.create(
            {
                "buffer_profile_id": cls.buffer_profile_distr.id,
                "product_id": cls.product_distributed.id,
                "location_id": cls.stock_location.id,
                "warehouse_id": cls.warehouse.id,
                "qty_multiple": 1.0,
                "adu_calculation_method": cls.adu_fixed.id,
                "adu_fixed": 5.0,
                "lead_days": 20,
            }
        )

        # dates for a period of 120 days for estimates.
        cls.estimate_date_from = cls.calendar.plan_days(1, datetime.today()).date()
        days = 119
        dt = cls.calendar.plan_days(+1 * days + 1, datetime.today())
        cls.estimate_date_to = dt.date()

        # Run cron jobs:
        cls.bufferModel.cron_ddmrp_adu()
        cls.bufferModel.cron_ddmrp()

    @classmethod
    def _create_user(cls, login, groups):
        """Create a user."""
        group_ids = [group.id for group in groups]
        user = cls.user_model.with_context(no_reset_password=True).create(
            {
                "name": "Test User",
                "login": login,
                "password": "demo",
                "email": "test@yourcompany.com",
                "groups_id": [(6, 0, group_ids)],
            }
        )
        return user

    def create_pickingoutA(self, date_move, qty, uom=False, source_location=None):
        if not uom:
            uom = self.productA.uom_id
        if not source_location:
            source_location = self.binA
        picking = self.pickingModel.with_user(self.user).create(
            {
                "picking_type_id": self.picking_type_out.id,
                "location_id": source_location.id,
                "location_dest_id": self.customer_location.id,
                "scheduled_date": date_move,
                "move_ids": [
                    (
                        0,
                        0,
                        {
                            "name": "Test move",
                            "product_id": self.productA.id,
                            "date": date_move,
                            "product_uom": uom.id,
                            "product_uom_qty": qty,
                            "location_id": source_location.id,
                            "location_dest_id": self.customer_location.id,
                        },
                    )
                ],
            }
        )
        picking.action_confirm()
        return picking

    def create_pickinginA(self, date_move, qty):
        picking = self.pickingModel.with_user(self.user).create(
            {
                "picking_type_id": self.picking_type_in.id,
                "location_id": self.supplier_location.id,
                "location_dest_id": self.binA.id,
                "scheduled_date": date_move,
                "move_ids": [
                    (
                        0,
                        0,
                        {
                            "name": "Test move",
                            "product_id": self.productA.id,
                            "date": date_move,
                            "date_deadline": date_move,
                            "product_uom": self.productA.uom_id.id,
                            "product_uom_qty": qty,
                            "location_id": self.supplier_location.id,
                            "location_dest_id": self.binA.id,
                        },
                    )
                ],
            }
        )
        picking.action_confirm()
        return picking

    def create_pickinginternalA(self, date_move, qty):
        picking = self.pickingModel.with_user(self.user).create(
            {
                "picking_type_id": self.picking_type_internal.id,
                "location_id": self.binA.id,
                "location_dest_id": self.binB.id,
                "scheduled_date": date_move,
                "move_ids": [
                    (
                        0,
                        0,
                        {
                            "name": "Test move",
                            "product_id": self.productA.id,
                            "date": date_move,
                            "product_uom": self.productA.uom_id.id,
                            "product_uom_qty": qty,
                            "location_id": self.binA.id,
                            "location_dest_id": self.binB.id,
                        },
                    )
                ],
            }
        )
        picking.action_confirm()
        return picking

    def create_picking_out(self, product, date_move, qty, source_location=None):
        if not source_location:
            source_location = self.binA
        picking = self.pickingModel.with_user(self.user).create(
            {
                "picking_type_id": self.picking_type_out.id,
                "location_id": source_location.id,
                "location_dest_id": self.customer_location.id,
                "scheduled_date": date_move,
                "move_ids": [
                    (
                        0,
                        0,
                        {
                            "name": "Test move",
                            "product_id": product.id,
                            "date": date_move,
                            "product_uom": product.uom_id.id,
                            "product_uom_qty": qty,
                            "location_id": source_location.id,
                            "location_dest_id": self.customer_location.id,
                        },
                    )
                ],
            }
        )
        picking.action_confirm()
        return picking

    def create_picking_in(self, product, date_move, qty):
        picking = self.pickingModel.with_user(self.user).create(
            {
                "picking_type_id": self.picking_type_in.id,
                "location_id": self.supplier_location.id,
                "location_dest_id": self.binA.id,
                "scheduled_date": date_move,
                "move_ids": [
                    (
                        0,
                        0,
                        {
                            "name": "Test move",
                            "product_id": product.id,
                            "date": date_move,
                            "product_uom": product.uom_id.id,
                            "product_uom_qty": qty,
                            "location_id": self.supplier_location.id,
                            "location_dest_id": self.binA.id,
                        },
                    )
                ],
            }
        )
        picking.action_confirm()
        return picking

    def _do_picking(self, picking, date):
        """Do picking with only one move on the given date."""
        picking.action_confirm()
        picking.move_ids.quantity_done = picking.move_ids.product_uom_qty
        picking._action_done()
        for move in picking.move_ids:
            move.date = date

    def create_orderpoint_procurement(self, buffer, make_procurement=True):
        """Make Procurement from Reordering Rule"""
        wizard = (
            self.make_procurement_wiz.with_user(self.user)
            .with_context(
                active_model="stock.buffer", active_ids=buffer.ids, active_id=buffer.id
            )
            .create({})
        )
        if make_procurement:
            wizard.make_procurement()
        return wizard

    def create_inventorylossA(self, date_move, qty):
        move = self.moveModel.with_user(self.user).create(
            {
                "name": "Test inventory move",
                "product_id": self.productA.id,
                "date": date_move,
                "product_uom": self.productA.uom_id.id,
                "product_uom_qty": qty,
                "location_id": self.binA.id,
                "location_dest_id": self.inventory_location.id,
            },
        )
        return move

    def _do_move(self, move, date):
        move._action_confirm()
        move.move_line_ids.qty_done = move.move_line_ids.reserved_uom_qty
        move._action_done()
        move.date = date
