from contextlib import contextmanager
import json
from lxml import etree
import requests
from unittest.mock import patch

from odoo import Command, fields
from odoo.exceptions import UserError
from odoo.tests import tagged
from odoo.tools import file_open

from odoo.addons.account.tests.common import PatchRequestsMixin
from odoo.addons.account.tests.test_account_move_send import TestAccountMoveSendCommon
from odoo.addons.l10n_hr_edi.tests.test_hr_edi_common import TestL10nHrEdiCommon


@tagged('post_install_l10n', 'post_install', '-at_install')
class TestHrEdiFlowsMocked(TestL10nHrEdiCommon, TestAccountMoveSendCommon, PatchRequestsMixin):

    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        company = cls.company_data['company']
        company.write({
            'l10n_hr_mer_username': '...',
            'l10n_hr_mer_password': '...',
            'l10n_hr_mer_company_ident': '12345678',
            'l10n_hr_mer_software_ident': 'Saodoo-001',
            'l10n_hr_mer_connection_state': 'active',
        })

    def assertRequestsEqual(self, actual_request, expected_request):
        """ Override so that when we check the arguments to `requests.Session.request`,
            we use `assertXmlTreeEqual` on the XML documents passed as argument, to benefit
            from ___ignore___ and better error messages.
        """

        if (
            (actual_file := actual_request.get('json', {}).get('File'))
            and (expected_file := expected_request.get('json', {}).get('File'))
        ):
            @contextmanager
            def restore_files():
                try:
                    actual_request['json']['File'] = expected_request['json']['File'] = 'Placeholder for comparison'
                    yield
                finally:
                    actual_request['json']['File'] = actual_file
                    expected_request['json']['File'] = expected_file

            with restore_files():
                super().assertRequestsEqual(actual_request, expected_request)
                self.assertXmlTreeEqual(
                    etree.fromstring(actual_file.encode()),
                    etree.fromstring(expected_file.encode())
                )
        else:
            super().assertRequestsEqual(actual_request, expected_request)

    # -------------------------------------------------------------------------
    # Helpers
    # -------------------------------------------------------------------------

    def _get_base_url(self):
        return 'https://demo.moj-eracun.hr'

    def _build_request(self, endpoint, request_args=None):
        return {
            'method': 'post',
            'url': self._get_base_url() + endpoint,
            'headers': {'charset': 'utf-8', 'content-type': 'application/json'},
            'data': None,
            'json': {
                'Username': self.env.company.l10n_hr_mer_username,
                'Password': self.env.company.l10n_hr_mer_password,
                'CompanyId': self.env.company.l10n_hr_mer_company_ident,
                'SoftwareId': self.env.company.l10n_hr_mer_software_ident,
                **(request_args or {}),
            },
            'timeout': 30,
        }

    def _build_response(self, status_code, response_json=None, response_content=None):
        response = requests.Response()
        response.status_code = status_code
        if response_json is not None:
            response._content = json.dumps(response_json).encode()
        elif response_content is not None:
            response._content = response_content
        return response

    # -------------------------------------------------------------------------
    # Sending flow
    # -------------------------------------------------------------------------

    def test_10_send_invoice(self):
        self.setup_partner_as_hr(self.env.company.partner_id)
        self.setup_partner_as_hr_alt(self.partner_a)
        tax = self.env['account.chart.template'].ref('VAT_S_IN_ROC_25')

        # 1. Create invoice

        invoice = self.env['account.move'].create({
            'invoice_date': '2025-01-01',
            'date': '2025-01-01',
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'invoice_line_ids': [
                Command.create({
                    'product_id': self.product_a.id,
                    'price_unit': 100.0,
                    'tax_ids': [Command.set(tax.ids)],
                }),
            ],
        })
        invoice.action_post()

        self.assertRecordValues(invoice, [{
            'l10n_hr_process_type': 'P1',
            'l10n_hr_fiscal_user_id': self.env.user.partner_id.id,
            'l10n_hr_operator_name': 'Because I am accountman!',
            'l10n_hr_operator_oib': '01234567896',
        }])

        # 2. Send invoice to MER

        send_and_print = self.create_send_and_print(invoice)

        with file_open('l10n_hr_edi/tests/flows/out_invoice.xml', 'r') as f:
            expected_invoice_xml = f.read()

        with patch(
            'odoo.addons.account_peppol.models.res_partner.ResPartner._peppol_lookup_participant', return_value=None
        ), self.assertRequests([
            (
                # Request 1: Send invoice
                self._build_request(
                    endpoint='/apis/v2/send',
                    request_args={
                        'File': expected_invoice_xml,
                    },
                ),
                self._build_response(
                    status_code=200,
                    response_json={
                        'ElectronicId': '3083666',
                        'DocumentNr': invoice.name,
                        'DocumentTypeId': 1,
                        'DocumentTypeName': 'Račun',
                        'StatusId': 20,
                        'StatusName': 'Obrađen',
                        'RecipientBusinessNumber': 'BE0477472701',
                        'RecipientBusinessUnit': '',
                        'RecipientBusinessName': 'Odoo S.A.',
                        'Created': '2025-10-14T14:27:06.2388492+02:00',
                        'Sent': '2025-10-14T14:27:06.3355877+02:00',
                        'Modified': '2025-10-14T14:27:06.3355877+02:00',
                        'Delivered': None,
                    }
                ),
            ),
        ]):
            send_and_print._generate_and_send_invoices(invoice)

        self.assertRecordValues(invoice, [{
            'l10n_hr_mer_document_status': '20',
            'l10n_hr_mer_document_eid': '3083666',
        }])

    def test_send_invoice_error(self):
        self.setup_partner_as_hr(self.env.company.partner_id)
        self.setup_partner_as_hr_alt(self.partner_a)
        tax = self.env['account.chart.template'].ref('VAT_S_IN_ROC_25')

        invoice = self.env['account.move'].create({
            'invoice_date': '2025-01-01',
            'date': '2025-01-01',
            'move_type': 'out_invoice',
            'partner_id': self.partner_a.id,
            'invoice_line_ids': [
                Command.create({
                    'product_id': self.product_a.id,
                    'price_unit': 100.0,
                    'tax_ids': [Command.set(tax.ids)],
                }),
            ],
        })
        invoice.action_post()

        self.assertRecordValues(invoice, [{
            'l10n_hr_process_type': 'P1',
            'l10n_hr_fiscal_user_id': self.env.user.partner_id.id,
            'l10n_hr_operator_name': 'Because I am accountman!',
            'l10n_hr_operator_oib': '01234567896',
        }])

        send_and_print = self.create_send_and_print(invoice)

        with file_open('l10n_hr_edi/tests/flows/out_invoice.xml', 'r') as f:
            expected_invoice_xml = f.read()

        with patch(
            'odoo.addons.account_peppol.models.res_partner.ResPartner._peppol_lookup_participant', return_value=None
        ), self.assertRaisesRegex(UserError, r"MER service returned an error: Username '12513': Korisničko ime i lozinka nisu ispravni\.\. Trace ID: 4f701362-96cc-49c6-a297-854e740ad719\."):
            with self.assertRequests([
                (
                    # Request 1: Send invoice - error should be triggered
                    self._build_request(
                        endpoint='/apis/v2/send',
                        request_args={
                            'File': expected_invoice_xml,
                        },
                    ),
                    self._build_response(
                        status_code=200,
                        response_json={
                            'Username':
                                {
                                    'Value': '12513',
                                    'Messages': ['Korisničko ime i lozinka nisu ispravni.. Trace ID: 4f701362-96cc-49c6-a297-854e740ad719.']
                                }
                            }
                    ),
                ),
            ]):
                send_and_print._generate_and_send_invoices(invoice)

    # -------------------------------------------------------------------------
    # Receiving flow
    # -------------------------------------------------------------------------

    def test_20_receive_invoice(self):
        self.setup_partner_as_hr(self.env.company.partner_id)
        self.modify_demo_bank_account()

        # 1. Fetch inbound invoices

        with file_open('l10n_hr_edi/tests/flows/in_invoice.xml', 'rb') as f:
            mocked_in_invoice_bytes = f.read()

        with self.assertRequests([
            (
                # Request 1: queryInbox
                self._build_request(endpoint='/apis/v2/queryInbox'),
                self._build_response(
                    status_code=200,
                    response_json=[{
                        'ElectronicId': 3148006,
                        'DocumentNr': '2/1/1',
                        'DocumentTypeId': 1,
                        'DocumentTypeName': 'Račun',
                        'StatusId': 40,
                        'StatusName': 'Preuzet',
                        'SenderBusinessNumber': 'BE0477472701',
                        'SenderBusinessUnit': None,
                        'SenderBusinessName': 'Odoo S.A.',
                        'Updated': '2025-12-29T09:37:04.5605292',
                        'Sent': '2025-12-24T10:31:17.5989975',
                        'Delivered': '2025-12-29T09:37:04.5605231',
                        'Imported': False
                    }]
                )
            ),
            (
                # Request 2: statusInbox
                self._build_request(
                    endpoint='/api/fiscalization/statusInbox',
                    request_args={
                        'ElectronicId': '3148006',
                    }
                ),
                self._build_response(
                    status_code=200,
                    response_json=[{
                        'electronicId': 762506406,
                        'senderName': 'Mock d.o.o.',
                        'senderIdentifierValue': '12345678901',
                        'channelType': 1,
                        'channelTypeDescription': 'eIzvještavanje',
                        'businessStatusReason': 'Mock business status reason',
                        'messages': [{
                            'status': 0,
                            'statusDescription': 'Uspjeh',
                            'fiscalizationRequestId': '73743778-d386-466b-938f-020831fa9092',
                            'dateOfFiscalization': '2025-12-29T16:15:29.3745898+01:00',
                            'message': 'Mock fiscalization successful',
                            'encodedXml': 'TW9jayBYTUwgY29udGVudCBmb3IgZmlzY2FsaXphdGlvbiBzdGF0dXMgcmVzcG9uc2U=',
                            'errorCode': 'S001',
                            'errorCodeDescription': 'Sistemska greška prilikom obrade zahtjeva',
                            'messageType': 0,
                            'messageTypeDescription': 'Kao POŠILJATELJ dohvati status fiskalizacijske poruke'
                        }]
                    }]
                )
            ),
            (
                # Request 3: queryDocumentProcessStatusInbox
                self._build_request(
                    endpoint='/apis/v2/queryDocumentProcessStatusInbox',
                    request_args={
                        'ElectronicId': '3148006',
                    }
                ),
                self._build_response(
                    status_code=200,
                    response_json=[{
                        'ElectronicId': 3148006,
                        'DocumentNr': '2/1/1',
                        'DocumentTypeId': 1,
                        'DocumentTypeName': 'Račun',
                        'StatusId': 40,
                        'StatusName': 'Preuzet',
                        'SenderBusinessNumber': 'BE0477472701',
                        'SenderBusinessUnit': None,
                        'SenderBusinessName': 'Odoo S.A.',
                        'Sent': '2025-12-24T10:31:17.5989975',
                        'Delivered': '2025-12-29T09:37:04.5605231',
                        'IssueDate': '2025-12-24T00:00:00',
                        'DocumentProcessStatusId': 4,
                        'DocumentProcessStatusName': 'Potvrda zaprimanja',
                        'AdditionalDokumentStatusId': None,
                        'RejectReason': None,
                        'InboundFiscalizationStatus': None
                    }]
                )
            ),
            (
                # Request 4: receive
                self._build_request(
                    endpoint='/apis/v2/receive',
                    request_args={
                        'ElectronicId': '3148006',
                    }
                ),
                self._build_response(
                    status_code=200,
                    response_content=mocked_in_invoice_bytes
                )
            ),
        ]):
            self.company_data['default_journal_purchase'].l10n_hr_mer_get_new_documents_all()

        fetched_invoice = self.env['account.move'].search([('l10n_hr_mer_document_eid', '=', '3148006')])

        self.assertRecordValues(fetched_invoice, [{
            'ref': '2/1/1',
            'payment_reference': 'HR00 211',
            'invoice_date': fields.Date.from_string('2025-12-24'),
            'invoice_date_due': fields.Date.from_string('2025-12-24'),
            'amount_untaxed': 47.0,
            'amount_tax': 6.11,
            'amount_total': 53.11,
            'l10n_hr_mer_document_eid': '3148006',
            'l10n_hr_mer_document_status': '40',
            'l10n_hr_process_type': 'P1',
            'l10n_hr_business_document_status': '4',
            'l10n_hr_fiscalization_number': '2/1/1',
            'l10n_hr_fiscalization_status': '0',
            'l10n_hr_fiscalization_error': 'S001 - Sistemska greška prilikom obrade zahtjeva',
            'l10n_hr_fiscalization_request': '73743778-d386-466b-938f-020831fa9092',
            'l10n_hr_fiscalization_channel_type': '1',
        }])

        self.assertRecordValues(fetched_invoice.partner_id, [{
            'name': 'HR Company',
            'vat': 'HR18724543544',
            'street': 'Vukasi',
            'city': 'Ježdovec',
            'zip': '10250',
            'country_id': self.env.ref('base.hr').id,
        }])

        self.assertRecordValues(fetched_invoice.invoice_line_ids, [{
            'name': '[E-COM10] Pedal Bin',
            'quantity': 1.0,
            'price_unit': 47.0,
        }])

        self.assertEqual(fetched_invoice.invoice_line_ids.tax_ids.amount, 13.0)

    def test_query_inbox_error(self):
        self.setup_partner_as_hr(self.env.company.partner_id)

        with self.assertRaisesRegex(UserError, r"MER service returned an error: Username '12513': Korisničko ime i lozinka nisu ispravni."):
            with self.assertRequests([
                (
                    # Request 1: queryInbox - error should be triggered
                    self._build_request(endpoint='/apis/v2/queryInbox'),
                    self._build_response(
                        status_code=200,
                        response_json={
                            'Username': {
                                'Value': '12513',
                                'Messages': ['Korisničko ime i lozinka nisu ispravni.'],
                            },
                        }
                    )
                ),
            ]):
                self.company_data['default_journal_purchase'].l10n_hr_mer_get_new_documents_all()
