# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
from lxml import etree

from odoo import tools
from odoo.tests import tagged
from odoo.tools.misc import file_open
from odoo.addons.account.tests.common import AccountTestInvoicingCommon


@tagged('post_install_l10n', 'post_install', '-at_install')
class TestItEdi(AccountTestInvoicingCommon):

    class RepartitionLine:
        def __init__(self, factor_percent, repartition_type, tag_ids):
            self.factor_percent = factor_percent
            self.repartition_type = repartition_type
            self.tag_ids = tag_ids

    @classmethod
    def get_tag_ids(cls, tag_codes):
        """ Helper function to define tag ids for taxes """
        return cls.env['account.account.tag'].search([
            ('applicability', '=', 'taxes'),
            ('country_id.code', '=', 'IT'),
            ('name', 'in', tag_codes)]).ids

    @classmethod
    def repartition_lines(cls, *lines):
        """ Helper function to define repartition lines in taxes """
        return ([(5, 0, 0)] + [(0, 0, {
            **line.__dict__,
            'tag_ids': cls.get_tag_ids(line.tag_ids)
        }) for line in lines])

    @classmethod
    @AccountTestInvoicingCommon.setup_country('it')
    def setUpClass(cls):
        super().setUpClass()

        # Company data ------
        cls.company_data_2 = cls.setup_other_company(
            name='company_2_data',
            vat='IT01234560157',
            phone='0266766700',
            email='test@test.it',
            street="1234 Test Street",
            zip="12345",
            city="Prova",
            l10n_it_codice_fiscale='01234560157',
            l10n_it_tax_system="RF01",
        )
        cls.company = cls.company_data_2['company']
        cls.company.partner_id.write({
            'l10n_it_pa_index': "0803HR0"
        })

        cls.test_bank = cls.env['res.partner.bank'].create({
            'partner_id': cls.company.partner_id.id,
            'account_number': 'IT1212341234123412341234123',
            'bank_name': 'BIG BANK',
            'bank_bic': 'BIGGBANQ',
            'allow_out_payment': True,
        })

        # Partners
        cls.italian_partner_a = cls.env['res.partner'].create({
            'name': 'Alessi',
            'vat': 'IT00465840031',
            'l10n_it_codice_fiscale': '93026890017',
            'country_id': cls.env.ref('base.it').id,
            'street': 'Via Privata Alessi 6',
            'zip': '28887',
            'city': 'Milan',
            'company_id': False,
            'invoice_edi_format': 'it_edi_xml',
        })

        cls.italian_partner_b = cls.env['res.partner'].create({
            'name': 'pa partner',
            'vat': 'IT06655971007',
            'l10n_it_codice_fiscale': '06655971007',
            'l10n_it_pa_index': '123456',
            'country_id': cls.env.ref('base.it').id,
            'street': 'Via Test PA',
            'zip': '32121',
            'city': 'PA Town',
            'invoice_edi_format': 'it_edi_xml',
        })

        cls.italian_partner_no_address_codice = cls.env['res.partner'].create({
            'name': 'Alessi',
            'l10n_it_codice_fiscale': '00465840031',
        })

        cls.italian_partner_no_address_VAT = cls.env['res.partner'].create({
            'name': 'Alessi',
            'vat': 'IT00465840031',
        })

        cls.american_partner = cls.env['res.partner'].create({
            'name': 'Alessi',
            'vat': '00465840031',
            'country_id': cls.env.ref('base.us').id,
        })

        # We create this because we are unable to post without a proxy user existing
        cls.private_key_id = cls.env['certificate.key'].create({
            'name': 'IT test key',
            'content': base64.b64encode(file_open('l10n_it_edi/data/pkey.key', 'rb').read()),
        })
        cls.proxy_user = cls.env['account_edi_proxy_client.user'].create({
            'proxy_type': 'l10n_it_edi',
            'id_client': 'l10n_it_edi_test',
            'company_id': cls.company.id,
            'edi_identification': 'l10n_it_edi_test',
            'private_key_id': cls.private_key_id.id,
        })

        cls.default_tax = cls.env['account.tax'].with_company(cls.company).create({
            'name': "22% default",
            'amount': 22.0,
            'amount_type': 'percent',
        })

        cls.vat_0_N1_purchase = cls.env['account.tax'].with_company(cls.company).create({
            'name': "VAT 0% Natura N1",
            'amount': 0.0,
            'amount_type': 'percent',
            'type_tax_use': 'purchase',
            'l10n_it_exempt_reason': 'N1',
            'invoice_legal_notes': 'Test',
        })

        cls.vat_0_N2_1_purchase = cls.env['account.tax'].with_company(cls.company).create({
            'name': "VAT 0% Natura N2.1",
            'amount': 0.0,
            'amount_type': 'percent',
            'type_tax_use': 'purchase',
            'l10n_it_exempt_reason': 'N2.1',
            'invoice_legal_notes': 'Test',
        })

        cls.module = 'l10n_it_edi'

    def _remove_all_namespaces(self, root):
        """Strip XML namespaces and schemaLocation for deterministic test comparisons.

        While namespaces are fixed, schemaLocation can change across XSD versions.
        Since tests don't care about schema URLs or tag prefixes, stripping
        them keeps assertions focused purely on business data correctness.
        """
        for elem in root.iter(etree.Element):
            if isinstance(elem.tag, str) and elem.tag.startswith('{'):
                elem.tag = etree.QName(elem).localname
            for key in list(elem.attrib.keys()):
                if key.startswith('{'):
                    if "xmlns" in key or "schemaLocation" in key:
                        elem.attrib.pop(key)
                    else:
                        local_key = etree.QName(key).localname
                        elem.attrib[local_key] = elem.attrib.pop(key)
                elif key.startswith('xmlns'):
                    elem.attrib.pop(key)
        return root

    def _assert_export_invoice(self, invoice, filename):
        path = f'{self.module}/tests/export_xmls/{filename}'
        with tools.file_open(path, mode='rb') as fd:
            expected_tree = etree.fromstring(fd.read())
            expected_tree = self._remove_all_namespaces(expected_tree)

        xml = invoice._l10n_it_edi_render_xml()
        invoice_etree = etree.fromstring(xml)
        invoice_etree = self._remove_all_namespaces(invoice_etree)

        try:
            self.assertXmlTreeEqual(invoice_etree, expected_tree)
        except AssertionError as ae:
            ae.args = (ae.args[0] + f"\nFile used for comparison: {filename}", )
            raise

    def _assert_import_invoice(self, filename, expected_values_list, xml_to_apply=None, move_type="in_invoice"):
        """ Tests an invoice imported from an XML vendor bill file on the filesystem
            against expected values. XPATHs can be applied with the `xml_to_apply`
            argument to the XML content before it's imported.
        """
        path = f'{self.module}/tests/import_xmls/{filename}'
        with tools.file_open(path, mode='rb') as fd:
            import_content = fd.read()

        if xml_to_apply:
            tree = self.with_applied_xpath(
                etree.fromstring(import_content),
                xml_to_apply
            )
            import_content = etree.tostring(tree)

        attachment = self.env['ir.attachment'].create({
            'name': filename,
            'raw': import_content,
        })

        journal_type = {
            'in_invoice': 'default_journal_purchase',
            'out_invoice': 'default_journal_sale',
        }[move_type]
        purchase_journal = self.company_data_2[journal_type].with_context(default_move_type=move_type)
        invoices = purchase_journal._create_document_from_attachment(attachment.ids)

        expected_invoice_values_list = []
        expected_invoice_line_ids_values_list = []
        for expected_values in expected_values_list:
            invoice_values = dict(expected_values)
            if 'invoice_line_ids' in invoice_values:
                expected_invoice_line_ids_values_list += invoice_values.pop('invoice_line_ids')
            expected_invoice_values_list.append(invoice_values)
        try:
            self.assertRecordValues(invoices, expected_invoice_values_list)
            if expected_invoice_line_ids_values_list:
                self.assertRecordValues(invoices.invoice_line_ids, expected_invoice_line_ids_values_list)
        except AssertionError as ae:
            ae.args = (ae.args[0] + f"\nFile used for comparison: {filename}", )
            raise

        return invoices
