from odoo import Command
from odoo.tests import tagged

from .common import TestAccountReportsCommon


@tagged('post_install', '-at_install')
class TestAnalyticReport(TestAccountReportsCommon):

    @classmethod
    def setUpClass(cls):
        super().setUpClass()

        cls.env.user.groups_id += cls.env.ref(
            'analytic.group_analytic_accounting')
        cls.report = cls.env.ref('fusion_accounting.profit_and_loss')
        cls.report.write({'filter_analytic': True})

        cls.analytic_plan_parent = cls.env['account.analytic.plan'].create({
            'name': 'Plan Parent',
        })
        cls.analytic_plan_child = cls.env['account.analytic.plan'].create({
            'name': 'Plan Child',
            'parent_id': cls.analytic_plan_parent.id,
        })

        cls.analytic_account_parent = cls.env['account.analytic.account'].create({
            'name': 'Account 1',
            'plan_id': cls.analytic_plan_parent.id
        })
        cls.analytic_account_parent_2 = cls.env['account.analytic.account'].create({
            'name': 'Account 2',
            'plan_id': cls.analytic_plan_parent.id
        })
        cls.analytic_account_child = cls.env['account.analytic.account'].create({
            'name': 'Account 3',
            'plan_id': cls.analytic_plan_child.id
        })
        cls.analytic_account_parent_3 = cls.env['account.analytic.account'].create({
            'name': 'Account 4',
            'plan_id': cls.analytic_plan_parent.id
        })

    def test_report_group_by_analytic_plan(self):

        out_invoice = self.env['account.move'].create([{
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'date': '2019-05-01',
            'invoice_date': '2019-05-01',
            'invoice_line_ids': [
                Command.create({
                    'product_id': self.product_a.id,
                    'price_unit': 200.0,
                    'analytic_distribution': {
                        self.analytic_account_parent.id: 100,
                    },
                }),
                Command.create({
                    'product_id': self.product_b.id,
                    'price_unit': 200.0,
                    'analytic_distribution': {
                        self.analytic_account_child.id: 100,
                    },
                }),
            ]
        }])
        out_invoice.action_post()

        options = self._generate_options(
            self.report,
            '2019-01-01',
            '2019-12-31',
            default_options={
                'analytic_plans_groupby': [self.analytic_plan_parent.id, self.analytic_plan_child.id],
            }
        )

        lines = self.report._get_lines(options)

        self.assertLinesValues(
            # pylint: disable=bad-whitespace
            lines,
            [   0,                                   1,          2],
            [
              ['Revenue',                       400.00,     200.00],
              ['Less Costs of Revenue',           0.00,       0.00],
              ['Gross Profit',                  400.00,     200.00],
              ['Less Operating Expenses',         0.00,       0.00],
              ['Operating Income (or Loss)',    400.00,     200.00],
              ['Plus Other Income',               0.00,       0.00],
              ['Less Other Expenses',             0.00,       0.00],
              ['Net Profit',                    400.00,     200.00],
            ],
            options,
            currency_map={
                1: {'currency': self.env.company.currency_id},
                2: {'currency': self.env.company.currency_id},
            },
        )

    def test_report_analytic_filter(self):

        out_invoice = self.env['account.move'].create([{
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'date': '2023-02-01',
            'invoice_date': '2023-02-01',
            'invoice_line_ids': [
                Command.create({
                    'product_id': self.product_a.id,
                    'price_unit': 1000.0,
                    'analytic_distribution': {
                        self.analytic_account_parent.id: 100,
                    },
                })
            ]
        }])
        out_invoice.action_post()

        options = self._generate_options(
            self.report,
            '2023-01-01',
            '2023-12-31',
            default_options={
                'analytic_accounts': [self.analytic_account_parent.id],
            }
        )

        self.assertLinesValues(
            # pylint: disable=C0326
            # pylint: disable=bad-whitespace
            self.report._get_lines(options),
            [   0,                                 1],
            [
              ['Revenue',                    1000.00],
              ['Less Costs of Revenue',         0.00],
              ['Gross Profit',               1000.00],
              ['Less Operating Expenses',       0.00],
              ['Operating Income (or Loss)', 1000.00],
              ['Plus Other Income',             0.00],
              ['Less Other Expenses',           0.00],
              ['Net Profit',                 1000.00],
            ],
            options,
            currency_map={
                1: {'currency': self.env.company.currency_id},
                2: {'currency': self.env.company.currency_id},
            },
        )

        # Set the unused analytic account in filter, as no move is
        # using this account, the column should be empty
        options['analytic_accounts'] = [self.analytic_account_child.id]

        self.assertLinesValues(
            # pylint: disable=C0326
            # pylint: disable=bad-whitespace
            self.report._get_lines(options),
            [   0,                                 1],
            [
              ['Revenue',                       0.00],
              ['Less Costs of Revenue',         0.00],
              ['Gross Profit',                  0.00],
              ['Less Operating Expenses',       0.00],
              ['Operating Income (or Loss)',    0.00],
              ['Plus Other Income',             0.00],
              ['Less Other Expenses',           0.00],
              ['Net Profit',                    0.00],
            ],
            options,
            currency_map={
                1: {'currency': self.env.company.currency_id},
                2: {'currency': self.env.company.currency_id},
            },
        )

    def test_report_audit_analytic_filter(self):
        out_invoice = self.env['account.move'].create([{
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'date': '2023-02-01',
            'invoice_date': '2023-02-01',
            'invoice_line_ids': [
                Command.create({
                    'product_id': self.product_a.id,
                    'price_unit': 1000.0,
                    'analytic_distribution': {
                        self.analytic_account_parent.id: 100,
                    },
                }),
                Command.create({
                    'product_id': self.product_a.id,
                    'price_unit': 500.0,
                    'analytic_distribution': {
                        self.analytic_account_child.id: 100,
                    },
                }),
            ],
        }])
        out_invoice.action_post()

        options = self._generate_options(
            self.report,
            '2023-01-01',
            '2023-12-31',
            default_options={
                'analytic_accounts': [self.analytic_account_parent.id],
            }
        )

        lines = self.report._get_lines(options)

        report_line = self.report.line_ids[0]
        report_line_dict = next(x for x in lines if x['name'] == report_line.name)

        action_dict = self.report.action_audit_cell(
            options,
            self._get_audit_params_from_report_line(options, report_line, report_line_dict),
        )

        audited_lines = self.env['account.move.line'].search(action_dict['domain'])
        self.assertEqual(audited_lines, out_invoice.invoice_line_ids[0], "Only the line with the parent account should be shown")

    def test_report_analytic_groupby_and_filter(self):
        """
        Test that the analytic filter is applied on the groupby columns
        """

        out_invoice = self.env['account.move'].create([{
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'date': '2023-02-01',
            'invoice_date': '2023-02-01',
            'invoice_line_ids': [
                Command.create({
                    'product_id': self.product_a.id,
                    'price_unit': 1000.0,
                    'analytic_distribution': {
                        self.analytic_account_parent.id: 40,
                        self.analytic_account_child.id: 60,
                    },
                })
            ]
        }])
        out_invoice.action_post()

        # Test with only groupby
        options = self._generate_options(
            self.report,
            '2023-01-01',
            '2023-12-31',
            default_options={
                'analytic_accounts_groupby': [self.analytic_account_parent.id, self.analytic_account_child.id],
            }
        )

        self.assertLinesValues(
            # pylint: disable=C0326
            # pylint: disable=bad-whitespace
            self.report._get_lines(options),
            [   0,                                 1,        2,         3],
            [
              ['Revenue',                     400.00,   600.00,   1000.00],
              ['Less Costs of Revenue',         0.00,     0.00,      0.00],
              ['Gross Profit',                400.00,   600.00,   1000.00],
              ['Less Operating Expenses',       0.00,     0.00,      0.00],
              ['Operating Income (or Loss)',  400.00,   600.00,   1000.00],
              ['Plus Other Income',             0.00,     0.00,      0.00],
              ['Less Other Expenses',           0.00,     0.00,      0.00],
              ['Net Profit',                  400.00,   600.00,   1000.00],
            ],
            options,
            currency_map={
                1: {'currency': self.env.company.currency_id},
                2: {'currency': self.env.company.currency_id},
            },
        )

        # Adding analytic filter for the two analytic accounts used on the invoice line
        # The two groupby columns should still be filled
        options['analytic_accounts'] = [self.analytic_account_parent.id, self.analytic_account_child.id]

        self.assertLinesValues(
            # pylint: disable=C0326
            # pylint: disable=bad-whitespace
            self.report._get_lines(options),
            [   0,                                 1,        2,         3],
            [
              ['Revenue',                     400.00,   600.00,   1000.00],
              ['Less Costs of Revenue',         0.00,     0.00,      0.00],
              ['Gross Profit',                400.00,   600.00,   1000.00],
              ['Less Operating Expenses',       0.00,     0.00,      0.00],
              ['Operating Income (or Loss)',  400.00,   600.00,   1000.00],
              ['Plus Other Income',             0.00,     0.00,      0.00],
              ['Less Other Expenses',           0.00,     0.00,      0.00],
              ['Net Profit',                  400.00,   600.00,   1000.00],
            ],
            options,
            currency_map={
                1: {'currency': self.env.company.currency_id},
                2: {'currency': self.env.company.currency_id},
            },
        )
        # Keep only first analytic account on filter, the groupby column
        # for this account should still be filled, unlike the other
        options['analytic_accounts'] = [self.analytic_account_parent.id]

        self.assertLinesValues(
            # pylint: disable=C0326
            # pylint: disable=bad-whitespace
            self.report._get_lines(options),
            [   0,                                 1,        2,         3],
            [
              ['Revenue',                     400.00,     0.00,   1000.00],
              ['Less Costs of Revenue',         0.00,     0.00,      0.00],
              ['Gross Profit',                400.00,     0.00,   1000.00],
              ['Less Operating Expenses',       0.00,     0.00,      0.00],
              ['Operating Income (or Loss)',  400.00,     0.00,   1000.00],
              ['Plus Other Income',             0.00,     0.00,      0.00],
              ['Less Other Expenses',           0.00,     0.00,      0.00],
              ['Net Profit',                  400.00,     0.00,   1000.00],
            ],
            options,
            currency_map={
                1: {'currency': self.env.company.currency_id},
                2: {'currency': self.env.company.currency_id},
            },
        )

        # Keep only first analytic account on filter, the groupby column
        # for this account should still be filled, unlike the other
        options['analytic_accounts'] = [self.analytic_account_child.id]

        self.assertLinesValues(
            # pylint: disable=C0326
            # pylint: disable=bad-whitespace
            self.report._get_lines(options),
            [   0,                                 1,        2,         3],
            [
              ['Revenue',                       0.00,   600.00,   1000.00],
              ['Less Costs of Revenue',         0.00,     0.00,      0.00],
              ['Gross Profit',                  0.00,   600.00,   1000.00],
              ['Less Operating Expenses',       0.00,     0.00,      0.00],
              ['Operating Income (or Loss)',    0.00,   600.00,   1000.00],
              ['Plus Other Income',             0.00,     0.00,      0.00],
              ['Less Other Expenses',           0.00,     0.00,      0.00],
              ['Net Profit',                    0.00,   600.00,   1000.00],
            ],
            options,
            currency_map={
                1: {'currency': self.env.company.currency_id},
                2: {'currency': self.env.company.currency_id},
            },
        )

        # Set an unused analytic account in filter, all the columns
        # should be empty, as no move is using this account
        options['analytic_accounts'] = [self.analytic_account_parent_2.id]

        self.assertLinesValues(
            # pylint: disable=C0326
            # pylint: disable=bad-whitespace
            self.report._get_lines(options),
            [   0,                                 1,      2,      3],
            [
              ['Revenue',                       0.00,   0.00,   0.00],
              ['Less Costs of Revenue',         0.00,   0.00,   0.00],
              ['Gross Profit',                  0.00,   0.00,   0.00],
              ['Less Operating Expenses',       0.00,   0.00,   0.00],
              ['Operating Income (or Loss)',    0.00,   0.00,   0.00],
              ['Plus Other Income',             0.00,   0.00,   0.00],
              ['Less Other Expenses',           0.00,   0.00,   0.00],
              ['Net Profit',                    0.00,   0.00,   0.00],
            ],
            options,
            currency_map={
                1: {'currency': self.env.company.currency_id},
                2: {'currency': self.env.company.currency_id},
            },
        )

    def test_audit_cell_analytic_groupby_and_filter(self):
        """
        Test that the analytic filters are applied on the auditing of the cells
        """
        def _get_action_dict(options, column_index):
            lines = self.report._get_lines(options)
            report_line = self.report.line_ids[0]
            report_line_dict = next(x for x in lines if x['name'] == report_line.name)
            audit_param = self._get_audit_params_from_report_line(options, report_line, report_line_dict, column_group_key=list(options['column_groups'])[column_index])
            return self.report.action_audit_cell(options, audit_param)

        other_plan = self.env['account.analytic.plan'].create({'name': "Other Plan"})
        other_account = self.env['account.analytic.account'].create({'name': "Other Account", 'plan_id': other_plan.id, 'active': True})

        out_invoices = self.env['account.move'].create([
            {
                'move_type': 'out_invoice',
                'partner_id': self.partner_a.id,
                'date': '2023-02-01',
                'invoice_date': '2023-02-01',
                'invoice_line_ids': [
                    Command.create({
                        'product_id': self.product_a.id,
                        'price_unit': 1000.0,
                        'analytic_distribution': {
                            self.analytic_account_parent.id: 40,
                            self.analytic_account_child.id: 60,
                        }
                    }),
                ]
            },
            {
                'move_type': 'out_invoice',
                'partner_id': self.partner_a.id,
                'date': '2023-02-01',
                'invoice_date': '2023-02-01',
                'invoice_line_ids': [
                    Command.create({
                        'product_id': self.product_a.id,
                        'price_unit': 2000.0,
                        'analytic_distribution': {
                            f'{self.analytic_account_parent.id},{other_account.id}': 100,
                        },
                    }),
                ]
            }
        ])
        out_invoices.action_post()
        out_invoices = out_invoices.with_context(analytic_plan_id=self.analytic_plan_parent.id)
        analytic_lines_parent = out_invoices.invoice_line_ids.analytic_line_ids.filtered(lambda line: line.auto_account_id == self.analytic_account_parent)
        analytic_lines_other = out_invoices.with_context(analytic_plan_id=other_plan.id).invoice_line_ids.analytic_line_ids.filtered(lambda line: line.auto_account_id == other_account)

        # Test with only groupby
        options = self._generate_options(
            self.report,
            '2023-01-01',
            '2023-12-31',
            default_options={
                'analytic_accounts_groupby': [self.analytic_account_parent.id, other_account.id],
            }
        )
        action_dict = _get_action_dict(options, 0)  # First Column => Parent
        self.assertEqual(
            self.env['account.analytic.line'].search(action_dict['domain']),
            analytic_lines_parent,
            "Only the Analytic Line related to the Parent should be shown",
        )
        action_dict = _get_action_dict(options, 1)  # Second Column => Other
        self.assertEqual(
            self.env['account.analytic.line'].search(action_dict['domain']),
            analytic_lines_other,
            "Only the Analytic Line related to the Parent should be shown",
        )

        action_dict = _get_action_dict(options, 2)  # Third Column => AMLs
        self.assertEqual(
            out_invoices.line_ids.filtered_domain(action_dict['domain']),
            out_invoices.invoice_line_ids,
            "Both amls should be shown",
        )

        # Adding analytic filter for the two analytic accounts used on the invoice line
        options['analytic_accounts'] = [self.analytic_account_parent.id, other_account.id]
        action_dict = _get_action_dict(options, 0)  # First Column => Parent
        self.assertEqual(
            self.env['account.analytic.line'].search(action_dict['domain']),
            analytic_lines_parent,
            "Still only the Analytic Line related to the Parent should be shown",
        )
        action_dict = _get_action_dict(options, 1)  # Second Column => Other
        self.assertEqual(
            self.env['account.analytic.line'].search(action_dict['domain']),
            analytic_lines_other,
            "Still only the Analytic Line related to the Parent should be shown",
        )

        action_dict = _get_action_dict(options, 2)  # Third Column => AMLs
        self.assertEqual(
            out_invoices.line_ids.search(action_dict['domain']),
            out_invoices.invoice_line_ids,
            "Both amls should be shown",
        )

    def test_general_ledger_analytic_filter(self):
        analytic_plan = self.env["account.analytic.plan"].create({
            "name": "Default Plan",
        })
        analytic_account = self.env["account.analytic.account"].create({
            "name": "Test Account",
            "plan_id": analytic_plan.id,
        })

        invoice = self.init_invoice(
            "out_invoice",
            amounts=[100, 200],
            invoice_date="2023-01-01",
        )
        invoice.action_post()
        invoice.invoice_line_ids[0].analytic_distribution = {analytic_account.id: 100}

        general_ledger_report = self.env.ref("fusion_accounting.general_ledger_report")
        options = self._generate_options(
            general_ledger_report,
            "2023-01-01",
            "2023-01-01",
            default_options={
                'analytic_accounts': [analytic_account.id],
                'unfold_all': True,
            }
        )

        self.assertLinesValues(
            general_ledger_report._get_lines(options),
            #   Name                                    Debit           Credit          Balance
            [   0,                                      5,              6,              7],
            [
                ['400000 Product Sales',                0.00,           100.00,         -100.00],
                ['INV/2023/00001',                      0.00,           100.00,         -100.00],
                ['Total 400000 Product Sales',          0.00,           100.00,         -100.00],
                ['Total',                               0.00,           100.00,         -100.00],
            ],
            options,
        )

    def test_analytic_groupby_with_horizontal_groupby(self):

        out_invoice_1 = self.env['account.move'].create([{
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'date': '2024-07-01',
            'invoice_date': '2024-07-01',
            'invoice_line_ids': [
                Command.create({
                    'product_id': self.product_b.id,
                    'price_unit': 500.0,
                    'analytic_distribution': {
                        self.analytic_account_parent_2.id: 80,
                        self.analytic_account_parent_3.id: -10,
                    },
                }),
            ]
        }])
        out_invoice_1.action_post()

        out_invoice_2 = self.env['account.move'].create([{
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'date': '2024-07-01',
            'invoice_date': '2024-07-01',
            'invoice_line_ids': [
                Command.create({
                    'product_id': self.product_a.id,
                    'price_unit': 100.0,
                    'analytic_distribution': {
                        self.analytic_account_parent.id: 100,
                    },
                }),
            ]
        }])
        out_invoice_2.action_post()

        horizontal_group = self.env['account.report.horizontal.group'].create({
            'name': 'Horizontal Group Journal Entries',
            'report_ids': [self.report.id],
            'rule_ids': [
                Command.create({
                    'field_name': 'move_id',  # this field is specific to account.move.line and not in account.analytic.line
                    'domain': f"[('id', 'in', {(out_invoice_1 + out_invoice_2).ids})]",
                }),
            ],
        })

        options = self._generate_options(
            self.report,
            '2024-01-01',
            '2024-12-31',
            default_options={
                'analytic_accounts_groupby': [self.analytic_account_parent.id, self.analytic_account_parent_2.id, self.analytic_account_parent_3.id],
                'selected_horizontal_group_id': horizontal_group.id,
            }
        )

        self.assertLinesValues(
            self.report._get_lines(options),
            #   Horizontal groupby               [             Move 2              ]     [               Move 1                ]
            #   Analytic groupby                    A1          A2      A3      Balance     A1          A2       A3         Balance
            [   0,                                  1,          2,      3,      4,          5,          6,      7,          8],
            [
              ['Revenue',                       100.00,     0.00,   0.00,   100.00,     0.00,    400.00,    -50.00,     500.00],
              ['Less Costs of Revenue',           0.00,     0.00,   0.00,     0.00,     0.00,      0.00,      0.00,       0.00],
              ['Gross Profit',                  100.00,     0.00,   0.00,   100.00,     0.00,    400.00,    -50.00,     500.00],
              ['Less Operating Expenses',         0.00,     0.00,   0.00,     0.00,     0.00,      0.00,      0.00,       0.00],
              ['Operating Income (or Loss)',    100.00,     0.00,   0.00,   100.00,     0.00,    400.00,    -50.00,     500.00],
              ['Plus Other Income',               0.00,     0.00,   0.00,     0.00,     0.00,      0.00,      0.00,       0.00],
              ['Less Other Expenses',             0.00,     0.00,   0.00,     0.00,     0.00,      0.00,      0.00,       0.00],
              ['Net Profit',                    100.00,     0.00,   0.00,   100.00,     0.00,    400.00,    -50.00,     500.00],
            ],
            options,
        )

    def test_analytic_groupby_with_analytic_simulations(self):
        """
        Create an analytic simulation (analytic line without a move line)
        and check that it is taken into account in the report
        """

        self.env['account.analytic.line'].create({
            'name': 'Simulation',
            'date': '2019-05-01',
            'amount': 100.0,
            'unit_amount': 1.0,
            'company_id': self.env.company.id,
            self.analytic_plan_parent._column_name(): self.analytic_account_parent.id,
            'general_account_id': self.company_data['default_account_revenue'].id,
        })

        options = self._generate_options(
            self.report,
            '2019-01-01',
            '2019-12-31',
            default_options={
                'analytic_plans_groupby': [self.analytic_plan_parent.id, self.analytic_plan_child.id],
                'include_analytic_without_aml': True,
            }
        )

        self.assertLinesValues(
            self.report._get_lines(options),
            [   0,                                     1,          2],
            [
                ('Revenue',                       100.00,       0.00),
                ('Less Costs of Revenue',           0.00,       0.00),
                ('Gross Profit',                  100.00,       0.00),
                ('Less Operating Expenses',         0.00,       0.00),
                ('Operating Income (or Loss)',    100.00,       0.00),
                ('Plus Other Income',               0.00,       0.00),
                ('Less Other Expenses',             0.00,       0.00),
                ('Net Profit',                    100.00,       0.00),
            ],
            options,
        )

    def test_analytic_groupby_plans_without_analytic_accounts(self):
        """
        Ensure that grouping on several analytic plans without any analytic accounts works as expected
        """
        analytic_plans_without_accounts = self.env['account.analytic.plan'].create([
            {'name': 'Plan 1'},
            {'name': 'Plan 2'},
        ])

        options = self._generate_options(
            self.report, '2019-01-01', '2019-12-31',
            default_options={'analytic_plans_groupby': analytic_plans_without_accounts.ids}
        )

        self.assertEqual(
            len(options['column_groups']), 3,
            "the number of column groups should be 3, despite the 2 analytic plans having the exact same analytic accounts list"
        )

        self.assertLinesValues(
            self.report._get_lines(options),
            #                                     Plan 1        Plan 2         Total
            [   0,                                     1,          2,             3],
            [
                ('Revenue',                         0.00,       0.00,          0.00),
                ('Less Costs of Revenue',           0.00,       0.00,          0.00),
                ('Gross Profit',                    0.00,       0.00,          0.00),
                ('Less Operating Expenses',         0.00,       0.00,          0.00),
                ('Operating Income (or Loss)',      0.00,       0.00,          0.00),
                ('Plus Other Income',               0.00,       0.00,          0.00),
                ('Less Other Expenses',             0.00,       0.00,          0.00),
                ('Net Profit',                      0.00,       0.00,          0.00),
            ],
            options,
        )

    def test_profit_and_loss_multicompany_access_rights(self):
        branch = self.env['res.company'].create([{
            'name': "My Test Branch",
            'parent_id': self.env.company.id,
        }])
        other_currency = self.setup_other_currency('EUR', rounding=0.001)
        test_journal = self.env['account.journal'].create({
            'name': 'Test Journal',
            'code': 'TEST',
            'type': 'sale',
            'company_id': self.env.company.id,
            'currency_id': other_currency.id,
        })
        test_user = self.env['res.users'].create({
            'login': 'test',
            'name': 'The King',
            'email': 'noop@example.com',
            'groups_id': [Command.link(self.env.ref('account.group_account_manager').id)],
            'company_ids': [Command.link(self.env.company.id), Command.link(branch.id)],
        })
        self.env.invalidate_all()

        options = self._generate_options(
            self.report.with_user(test_user).with_company(branch), '2019-01-01', '2019-12-31',
        )
        lines = self.report._get_lines(options)
        self.assertTrue(lines)
        self.assertEqual(test_journal.display_name, "Test Journal (EUR)")
