# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo.exceptions import ValidationError
from odoo.tests.common import TransactionCase
from odoo.tests import Form


class TestMrpAnalyticAccount(TransactionCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        # The group 'mrp.group_mrp_routings' is required to make the field
        # 'workorder_ids' visible in the view of 'mrp.production'. The subviews
        #  of `workorder_ids` must be present in many tests to create records.
        cls.env.user.groups_id += (
            cls.env.ref('analytic.group_analytic_accounting')
            + cls.env.ref('mrp.group_mrp_routings')
        )

        cls.analytic_plan = cls.env['account.analytic.plan'].create({
            'name': 'Plan',
        })
        cls.applicability = cls.env['account.analytic.applicability'].create({
            'business_domain': 'general',
            'analytic_plan_id': cls.analytic_plan.id,
            'applicability': 'mandatory',
        })
        cls.analytic_account = cls.env['account.analytic.account'].create({
            'name': 'test_analytic_account',
            'plan_id': cls.analytic_plan.id,
        })
        cls.workcenter = cls.env['mrp.workcenter'].create({
            'name': 'Workcenter',
            'default_capacity': 1,
            'time_efficiency': 100,
            'costs_hour': 10,
        })
        cls.product = cls.env['product.product'].create({
            'name': 'Product',
            'type': 'product',
            'standard_price': 233.0,
        })
        cls.component = cls.env['product.product'].create({
            'name': 'Component',
            'type': 'product',
            'standard_price': 10.0,
        })
        cls.bom = cls.env['mrp.bom'].create({
            'product_id': cls.product.id,
            'product_tmpl_id': cls.product.product_tmpl_id.id,
            'product_qty': 1.0,
            'type': 'normal',
            'bom_line_ids': [
                (0, 0, {'product_id': cls.component.id, 'product_qty': 1.0}),
            ],
            'operation_ids': [
                (0, 0, {'name': 'work work', 'workcenter_id': cls.workcenter.id, 'time_cycle': 15, 'sequence': 1}),
            ]})


class TestAnalyticAccount(TestMrpAnalyticAccount):
    def test_mo_analytic(self):
        """Test the amount on analytic line will change when consumed qty of the
        component changed.
        """
        # create a mo
        mo_form = Form(self.env['mrp.production'])
        mo_form.product_id = self.product
        mo_form.bom_id = self.bom
        mo_form.product_qty = 10.0
        mo_form.analytic_distribution = {str(self.analytic_account.id): 100.0}
        mo = mo_form.save()
        mo.action_confirm()
        self.assertEqual(mo.state, 'confirmed')
        self.assertEqual(len(mo.move_raw_ids.analytic_account_line_ids), 0)
        # increase qty_producing to 5.0
        mo_form = Form(mo)
        mo_form.qty_producing = 5.0
        mo_form.save()
        self.assertEqual(mo.state, 'progress')
        self.assertEqual(mo.move_raw_ids.analytic_account_line_ids.amount, -50.0)

        # increase qty_producing to 10.0
        mo_form = Form(mo)
        mo_form.qty_producing = 10.0
        mo_form.save()
        mo.workorder_ids.button_finish()
        self.assertEqual(mo.state, 'to_close')
        self.assertEqual(mo.move_raw_ids.analytic_account_line_ids.amount, -100.0)

        # mark as done
        mo.button_mark_done()
        self.assertEqual(mo.state, 'done')
        self.assertEqual(mo.move_raw_ids.analytic_account_line_ids.amount, -100.0)

    def test_mo_analytic_backorder(self):
        """Test the analytic lines are correctly posted when backorder.
        """
        # create a mo
        mo_form = Form(self.env['mrp.production'])
        mo_form.product_id = self.product
        mo_form.bom_id = self.bom
        mo_form.product_qty = 10.0
        mo_form.analytic_distribution = {str(self.analytic_account.id): 100.0}
        mo = mo_form.save()
        mo.action_confirm()
        self.assertEqual(mo.state, 'confirmed')
        self.assertEqual(len(mo.move_raw_ids.analytic_account_line_ids), 0)

        # increase qty_producing to 5.0
        mo_form = Form(mo)
        mo_form.qty_producing = 5.0
        mo_form.save()
        self.assertEqual(mo.state, 'progress')
        self.assertEqual(mo.move_raw_ids.analytic_account_line_ids.amount, -50.0)

        backorder_wizard_dict = mo.button_mark_done()
        Form(self.env[(backorder_wizard_dict.get('res_model'))].with_context(backorder_wizard_dict['context'])).save().action_backorder()
        self.assertEqual(mo.state, 'done')
        self.assertEqual(mo.move_raw_ids.analytic_account_line_ids.amount, -50.0)

    def test_workcenter_different_analytic_account(self):
        """Test when workcenter and MO are using the same analytic account, no
        duplicated lines will be post.
        """
        # Required for `workorder_ids` to be visible in the view
        self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings')
        # set wc analytic account to be different from the one on the bom
        analytic_plan = self.env['account.analytic.plan'].create({'name': 'Plan Test'})
        wc_analytic_account = self.env['account.analytic.account'].create({'name': 'wc_analytic_account', 'plan_id': analytic_plan.id})
        self.workcenter.analytic_distribution = {str(wc_analytic_account.id): 100.0}

        # create a mo
        mo_form = Form(self.env['mrp.production'])
        mo_form.product_id = self.product
        mo_form.bom_id = self.bom
        mo_form.product_qty = 10.0
        mo_form.analytic_distribution = {str(self.analytic_account.id): 100.0}
        mo = mo_form.save()
        mo.action_confirm()
        self.assertEqual(len(mo.workorder_ids.wc_analytic_account_line_ids), 0)

        # change duration to 60
        mo_form = Form(mo)
        with mo_form.workorder_ids.edit(0) as line_edit:
            line_edit.duration = 60.0
        mo_form.save()
        self.assertEqual(mo.workorder_ids.mo_analytic_account_line_ids.amount, -10.0)
        self.assertEqual(mo.workorder_ids.mo_analytic_account_line_ids[self.analytic_plan._column_name()], self.analytic_account)
        self.assertEqual(mo.workorder_ids.wc_analytic_account_line_ids.amount, -10.0)
        self.assertEqual(mo.workorder_ids.wc_analytic_account_line_ids[analytic_plan._column_name()], wc_analytic_account)

        # change duration to 120
        with mo_form.workorder_ids.edit(0) as line_edit:
            line_edit.duration = 120.0
        mo_form.save()
        self.assertEqual(mo.workorder_ids.mo_analytic_account_line_ids.amount, -20.0)
        self.assertEqual(mo.workorder_ids.mo_analytic_account_line_ids[self.analytic_plan._column_name()], self.analytic_account)
        self.assertEqual(mo.workorder_ids.wc_analytic_account_line_ids.amount, -20.0)
        self.assertEqual(mo.workorder_ids.wc_analytic_account_line_ids[analytic_plan._column_name()], wc_analytic_account)

        # mark as done
        mo_form.qty_producing = 10.0
        mo_form.save()
        mo.button_mark_done()
        self.assertEqual(mo.state, 'done')
        self.assertEqual(mo.workorder_ids.mo_analytic_account_line_ids.amount, -20.0)
        self.assertEqual(mo.workorder_ids.mo_analytic_account_line_ids[self.analytic_plan._column_name()], self.analytic_account)
        self.assertEqual(mo.workorder_ids.wc_analytic_account_line_ids.amount, -20.0)
        self.assertEqual(mo.workorder_ids.wc_analytic_account_line_ids[analytic_plan._column_name()], wc_analytic_account)

    def test_changing_mo_analytic_account(self):
        """ Check if the MO account analytic lines are correctly updated
            after the change of the MO account analytic.
        """
        # Required for `workorder_ids` to be visible in the view
        self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings')
        # create a mo
        mo_form = Form(self.env['mrp.production'])
        mo_form.product_id = self.product
        mo_form.bom_id = self.bom
        mo_form.product_qty = 1
        mo_form.analytic_distribution = {str(self.analytic_account.id): 100.0}
        mo = mo_form.save()
        mo.action_confirm()
        self.assertEqual(mo.state, 'confirmed')
        self.assertEqual(len(mo.move_raw_ids.analytic_account_line_ids), 0)
        self.assertEqual(len(mo.workorder_ids.mo_analytic_account_line_ids), 0)

        # Change duration to 60
        mo_form = Form(mo)
        with mo_form.workorder_ids.edit(0) as line_edit:
            line_edit.duration = 60.0
        mo_form.save()
        self.assertEqual(mo.workorder_ids.mo_analytic_account_line_ids[self.analytic_plan._column_name()], self.analytic_account)

        # Mark as done
        mo.button_mark_done()
        self.assertEqual(mo.state, 'done')
        self.assertEqual(len(mo.move_raw_ids.analytic_account_line_ids), 1)

        # Create a new analytic account
        analytic_plan = self.env['account.analytic.plan'].create({'name': 'Plan Test'})
        new_analytic_account = self.env['account.analytic.account'].create({'name': 'test_analytic_account_2', 'plan_id': analytic_plan.id})
        # Change the MO analytic account
        mo.analytic_distribution = {str(new_analytic_account.id): 100.0}
        self.assertEqual(mo.move_raw_ids.analytic_account_line_ids[analytic_plan._column_name()], new_analytic_account)
        self.assertEqual(mo.workorder_ids.mo_analytic_account_line_ids[analytic_plan._column_name()], new_analytic_account)

        #Get the MO analytic account lines
        mo_analytic_account_raw_lines = mo.move_raw_ids.analytic_account_line_ids
        mo_analytic_account_wc_lines = mo.move_raw_ids.analytic_account_line_ids
        mo.analytic_distribution = {}
        # Check that the MO analytic account lines are deleted
        self.assertEqual(len(mo.move_raw_ids.analytic_account_line_ids), 0)
        self.assertEqual(len(mo.workorder_ids.mo_analytic_account_line_ids), 0)
        self.assertFalse(mo_analytic_account_raw_lines.exists())
        self.assertFalse(mo_analytic_account_wc_lines.exists())
        # Check that the AA lines are recreated correctly if we delete the AA, save the MO, and assign a new one
        mo.analytic_distribution = {str(self.analytic_account.id): 100.0}
        self.assertEqual(len(mo.move_raw_ids.analytic_account_line_ids), 1)
        self.assertEqual(len(mo.workorder_ids.mo_analytic_account_line_ids), 1)

    def test_add_remove_wo_analytic_no_company(self):
        """Test the addition and removal of work orders to a MO linked to
        an analytic account that has no company associated
        """
        # Create an analytic account and remove the company
        analytic_account_no_company = self.env['account.analytic.account'].create({
            'name': 'test_analytic_account_no_company',
            'plan_id': self.analytic_plan.id,
        }).with_context(analytic_plan_id=self.analytic_plan.id)
        analytic_account_no_company.company_id = False

        # Create a mo linked to an analytic account with no associated company
        mo_no_company = self.env['mrp.production'].create({
            'product_id': self.product.id,
            'analytic_distribution': {str(analytic_account_no_company.id): 100.0},
            'product_uom_id': self.bom.product_uom_id.id,
        })

        mo_no_c_form = Form(mo_no_company)
        wo = self.env['mrp.workorder'].create({
            'name': 'Work_order',
            'workcenter_id': self.workcenter.id,
            'product_uom_id': self.bom.product_uom_id.id,
            'production_id': mo_no_c_form.id,
            'duration': 60,
        })
        mo_no_c_form.save()
        self.assertTrue(mo_no_company.workorder_ids)
        self.assertEqual(wo.production_id.analytic_account_ids, analytic_account_no_company)
        self.assertEqual(len(analytic_account_no_company.line_ids), 1)
        mo_no_company.workorder_ids.unlink()
        self.assertEqual(len(analytic_account_no_company.line_ids), 0)

    def test_update_components_qty_to_0(self):
        """ Test that the analytic lines are deleted when the quantity of the component is set to 0.
            Create a Mo with analytic account and a component, confirm and validate it,
            set the quantity of the component to 0, the analytic lines should be deleted.
        """
        component = self.env['product.product'].create({
            'name': 'Component',
            'type': 'product',
            'standard_price': 100,
        })
        product = self.env['product.product'].create({
            'name': 'Product',
            'type': 'product',
        })
        bom = self.env['mrp.bom'].create({
                'product_tmpl_id': product.product_tmpl_id.id,
                'product_qty': 1,
                'product_uom_id': product.uom_id.id,
                'type': 'normal',
                'bom_line_ids': [(0, 0, {
                    'product_id': component.id,
                    'product_qty': 1,
                    'product_uom_id': component.uom_id.id,
                })],
        })
        analytic_account = self.env['account.analytic.account'].create({
            'name': "Test Account",
            'plan_id': self.analytic_plan.id,
        })
        mo_form = Form(self.env['mrp.production'])
        mo_form.product_id = product
        mo_form.bom_id = bom
        mo_form.product_qty = 1.0
        mo_form.analytic_distribution = {str(analytic_account.id): 100.0}
        mo = mo_form.save()
        mo.action_confirm()
        self.assertEqual(mo.state, 'confirmed')


        mo_form = Form(mo)
        mo_form.qty_producing = 1
        mo = mo_form.save()
        self.assertEqual(mo.state, 'to_close')
        mo.button_mark_done()
        self.assertEqual(mo.state, 'done')
        self.assertEqual(analytic_account.debit, 100)
        mo.move_raw_ids[0].quantity = 0
        self.assertEqual(analytic_account.debit, 0)
        self.assertFalse(analytic_account.line_ids)

    def test_cross_analytics(self):
        """ Test analytic distributions (AD) with cross analytics on an MO."""

        ap1 = self.env['account.analytic.plan'].create({
            'name': 'Plan 1',
        })
        ap2 = self.env['account.analytic.plan'].create({
            'name': 'Plan 2',
        })
        ac1A = self.env['account.analytic.account'].create({
            'name': 'test_cross_analytics plan 1 account A',
            'plan_id': ap1.id,
        })
        ac1B = self.env['account.analytic.account'].create({
            'name': 'test_cross_analytics plan 1 account B',
            'plan_id': ap1.id,
        })
        ac1C = self.env['account.analytic.account'].create({
            'name': 'test_cross_analytics plan 1 account C',
            'plan_id': ap1.id,
        })
        ac2A = self.env['account.analytic.account'].create({
            'name': 'test_cross_analytics plan 2 account A',
            'plan_id': ap2.id,
        })
        ac2B = self.env['account.analytic.account'].create({
            'name': 'test_cross_analytics plan 2 account B',
            'plan_id': ap2.id,
        })
        ac2C = self.env['account.analytic.account'].create({
            'name': 'test_cross_analytics plan 2 account C',
            'plan_id': ap2.id,
        })

        analytic_distribution = {
            f"{ac1A.id},{ac2A.id}": 33.33,
            f"{ac1B.id},{ac2B.id}": 33.33,
            f"{ac1C.id},{ac2C.id}": 33.33,
        }

        # create a mo
        mo_form = Form(self.env['mrp.production'])
        mo_form.product_id = self.product
        mo_form.bom_id = self.bom
        mo_form.product_qty = 10.0
        mo_form.analytic_distribution = analytic_distribution
        mo = mo_form.save()
        mo.action_confirm()
        self.assertEqual(mo.state, 'confirmed')
        self.assertEqual(len(mo.move_raw_ids.analytic_account_line_ids), 0)
        # increase qty_producing to 5.0
        mo_form = Form(mo)
        mo_form.qty_producing = 5.0
        mo_form.save()
        self.assertEqual(mo.state, 'progress')
        aals = mo.move_raw_ids.analytic_account_line_ids
        self.assertEqual(len(aals), 3)
        self.assertEqual(sum(aals.mapped('amount')), -49.99)

        # increase qty_producing to 10.0
        mo_form = Form(mo)
        mo_form.qty_producing = 10.0
        mo_form.save()
        mo.workorder_ids.button_finish()
        aals = mo.move_raw_ids.analytic_account_line_ids

        self.assertEqual(mo.state, 'to_close')
        self.assertEqual(len(aals), 3)
        self.assertEqual(sum(aals.mapped('amount')), -99.99)

        # mark as done
        mo.button_mark_done()
        aals = mo.move_raw_ids.analytic_account_line_ids
        self.assertEqual(mo.state, 'done')
        self.assertEqual(len(aals), 3)
        self.assertEqual(sum(aals.mapped('amount')), -99.99)

        # assert the right accounts are on the right analytic lines
        ap1_column = ap1._column_name()
        ap2_column = ap2._column_name()
        line_with_A_accounts = aals.filtered_domain([(ap1_column, '=', ac1A.id)])
        line_with_B_accounts = aals.filtered_domain([(ap1_column, '=', ac1B.id)])
        line_with_C_accounts = aals.filtered_domain([(ap1_column, '=', ac1C.id)])
        self.assertEqual(line_with_A_accounts[ap2_column], ac2A)
        self.assertEqual(line_with_B_accounts[ap2_column], ac2B)
        self.assertEqual(line_with_C_accounts[ap2_column], ac2C)

        # ensure the right amounts are on the right lines
        # the distribution calculation corrects for rounding error accumulation to ensure
        # the total sum is correct, so individual lines can have minor deviations
        self.assertAlmostEqual(line_with_A_accounts.amount, -100/3, delta=0.02)
        self.assertAlmostEqual(line_with_B_accounts.amount, -100/3, delta=0.02)
        self.assertAlmostEqual(line_with_B_accounts.amount, -100/3, delta=0.02)

    def test_mo_qty_analytics(self):
        """
        This test tests multiple behaviours and edge cases. First off, when
        an analytic distribution with an MO, the analytic entries should only
        be generated when the MO's components are consumed/reserved (i.e.
        picked). Second, when changing the produced quantity in a confirmed MO,
        it should appropriately adjust the amount of picked components. Third,
        analytic entries should at all times reflect the current situation, and
        thus must be regenerated every time there's a change in components'
        picked status.
        """
        # refill components
        location = self.env.ref('stock.stock_location_stock')
        self.env['stock.quant']._update_available_quantity(self.component, location, 10)

        # create a mo
        mo_form = Form(self.env['mrp.production'])
        mo_form.product_id = self.product
        mo_form.bom_id = self.bom
        mo_form.product_qty = 10.0
        mo_form.analytic_distribution = {str(self.analytic_account.id): 100.0}
        mo = mo_form.save()
        mo.action_confirm()
        self.assertEqual(mo.state, 'confirmed')
        self.assertEqual(self.analytic_account.balance, 0.0)

        # increase qty_producing to 5.0
        mo_form = Form(mo)
        mo_form.qty_producing = 5.0
        mo_form.save()
        self.assertEqual(mo.state, 'progress')
        self.assertEqual(self.analytic_account.balance, -50.0)

        # decrease qty_producing to 0.0
        mo_form = Form(mo)
        mo_form.qty_producing = 0.0
        mo_form.save()
        self.assertEqual(mo.state, 'progress')
        self.assertEqual(self.analytic_account.balance, 0.0)

    def test_mandatory_analytic_plan_production(self):
        """
        Tests that the distribution validation is correctly evaluated
        The Production (Manufacturing Order) creation should be constrained by an analytic applicability rule if any relevant one.
        """
        manufacturing_order = self.env['mrp.production'].create({
            'product_id': self.product.id,
        })
        self.assertTrue(manufacturing_order)

        self.applicability.business_domain = 'manufacturing_order'

        with self.assertRaises(ValidationError):
            self.env['mrp.production'].create({
                'product_id': self.product.id,
            })
        mo_analytic = self.env['mrp.production'].create({
            'product_id': self.product.id,
            'analytic_distribution': {str(self.analytic_account.id): 100.0},
        })
        self.assertTrue(mo_analytic)

    def test_mandatory_analytic_plan_bom(self):
        """
        Tests that the distribution validation is correctly evaluated
        The BOM creation should not be constrained by any analytic applicability rule.
        """
        bom = self.env['mrp.bom'].create({
            'product_tmpl_id': self.product.product_tmpl_id.id,
        })
        self.assertTrue(bom)

        self.applicability.business_domain = 'manufacturing_order'

        bom_2 = self.env['mrp.bom'].create({
            'product_tmpl_id': self.product.product_tmpl_id.id,
        })
        self.assertTrue(bom_2)

    def test_mandatory_analytic_plan_workcenter(self):
        """
        Tests that the distribution validation is correctly evaluated
        The Workcenter creation should not be constrained by any analytic applicability rule.
        """
        workcenter = self.env['mrp.workcenter'].create({
            'name': "Great Workcenter",
            'analytic_distribution': False,
        })
        self.assertTrue(workcenter)

        self.applicability.business_domain = 'manufacturing_order'

        workcenter_2 = self.env['mrp.workcenter'].create({
            'name': "Great Workcenter",
            'analytic_distribution': False,
        })
        self.assertTrue(workcenter_2)
