import base64
import requests
import uuid
from werkzeug.urls import url_encode

from odoo import _, api, fields, models
from odoo.exceptions import ValidationError

JOFOTARA_URL = "https://backend.jofotara.gov.jo/core/invoices/"


class AccountMove(models.Model):
    _inherit = 'account.move'

    l10n_jo_edi_uuid = fields.Char(string="Invoice UUID", copy=False, compute="_compute_l10n_jo_edi_uuid", store=True)
    l10n_jo_edi_qr = fields.Char(string="QR", copy=False)

    l10n_jo_edi_is_needed = fields.Boolean(
        compute="_compute_l10n_jo_edi_is_needed",
        help="Jordan: technical field to determine if this invoice is eligible to be e-invoiced.",
    )
    l10n_jo_edi_state = fields.Selection(
        selection=[('to_send', 'To Send'), ('sent', 'Sent'), ('demo', 'Sent (Demo)')],
        string="JoFotara State",
        tracking=True,
        copy=False)
    l10n_jo_edi_error = fields.Text(
        string="JoFotara Error",
        copy=False,
        readonly=True,
        help="Jordan: Error details.",
    )
    l10n_jo_edi_computed_xml = fields.Binary(
        string="Jordan E-Invoice computed XML File",
        compute="_compute_l10n_jo_edi_computed_xml",
        help="Jordan: technical field computing e-invoice XML data, useful at submission failure scenarios.",
    )
    l10n_jo_edi_xml_attachment_file = fields.Binary(
        string="Jordan E-Invoice XML File",
        copy=False,
        attachment=True,
        help="Jordan: technical field holding the e-invoice XML data.",
    )
    l10n_jo_edi_xml_attachment_id = fields.Many2one(
        comodel_name="ir.attachment",
        string="Jordan E-Invoice XML",
        compute=lambda self: self._compute_linked_attachment_id(
            "l10n_jo_edi_xml_attachment_id", "l10n_jo_edi_xml_attachment_file"
        ),
        depends=["l10n_jo_edi_xml_attachment_file"],
        help="Jordan: e-invoice XML.",
    )
    reversed_entry_id = fields.Many2one(tracking=True)
    l10n_jo_edi_invoice_type = fields.Selection(
        selection=[
            ('local', 'Local'),
            ('export', 'Export'),
            ('development', 'Development Area'),
        ],
        string="Invoice Type",
        precompute=True,
        compute='_compute_l10n_jo_edi_invoice_type',
        readonly=False, store=True,
        tracking=True,
        help="Invoice Types as per the Income and Sales Tax Department for JoFotara",
    )

    @api.depends("country_code", "move_type")
    def _compute_l10n_jo_edi_is_needed(self):
        for move in self:
            move.l10n_jo_edi_is_needed = (
                move.country_code == "JO"
                and move.move_type in ("out_invoice", "out_refund")
            )

    @api.depends("l10n_jo_edi_state")
    def _compute_show_reset_to_draft_button(self):
        # EXTENDS 'account'
        super()._compute_show_reset_to_draft_button()
        self.filtered(lambda move: move.l10n_jo_edi_state == 'sent').show_reset_to_draft_button = False

    @api.depends("l10n_jo_edi_is_needed")
    def _compute_l10n_jo_edi_uuid(self):
        for invoice in self:
            if invoice.l10n_jo_edi_is_needed and not invoice.l10n_jo_edi_uuid:
                invoice.l10n_jo_edi_uuid = uuid.uuid4()

    @api.depends("state", "l10n_jo_edi_is_needed")
    def _compute_l10n_jo_edi_computed_xml(self):
        for invoice in self:
            if invoice.state == 'posted' and invoice.l10n_jo_edi_is_needed:
                xml_content = self.env['account.edi.xml.ubl_21.jo']._export_invoice(invoice)[0]
                invoice.l10n_jo_edi_computed_xml = base64.b64encode(xml_content)
            else:
                invoice.l10n_jo_edi_computed_xml = False

    @api.depends('partner_id.country_code')
    def _compute_l10n_jo_edi_invoice_type(self):
        for move in self.filtered(lambda m: m.l10n_jo_edi_is_needed and m.l10n_jo_edi_invoice_type != 'development'):
            country_code = move.commercial_partner_id.country_code
            if country_code == 'JO':
                move.l10n_jo_edi_invoice_type = 'local'
            elif country_code:
                move.l10n_jo_edi_invoice_type = 'export'
            else:
                move.l10n_jo_edi_invoice_type = False

    @api.depends('partner_id', 'company_id')
    def _compute_preferred_payment_method_line_id(self):
        super()._compute_preferred_payment_method_line_id()

        for move in self.filtered(lambda m: m.partner_id and m.l10n_jo_edi_is_needed):
            expected_type = 'bank' if move.partner_id.is_company or move.partner_id.parent_id else 'cash'
            journal = self.env['account.journal'].search([
                ('type', '=', expected_type),
                ('company_id', '=', move.company_id.id),
                ('inbound_payment_method_line_ids', '!=', False),
            ], limit=1)
            if journal and (payment_method_line := journal.inbound_payment_method_line_ids[0]):
                move.preferred_payment_method_line_id = payment_method_line

    def download_l10n_jo_edi_computed_xml(self):
        if error_message := self._l10n_jo_validate_config() or self._l10n_jo_validate_fields():
            raise ValidationError(_("The following errors have to be fixed in order to create an XML:\n") + error_message)
        params = url_encode({
            'model': self._name,
            'id': self.id,
            'field': 'l10n_jo_edi_computed_xml',
            'filename': self._l10n_jo_edi_get_xml_attachment_name(),
            'mimetype': 'application/xml',
            'download': 'true',
        })
        return {'type': 'ir.actions.act_url', 'url': '/web/content/?' + params, 'target': 'new'}

    def _l10n_jo_qr_code_src(self):
        self.ensure_one()
        encoded_params = url_encode({
            'barcode_type': 'QR',
            'quiet': 0,
            'value': self.l10n_jo_edi_qr,
            'width': 200,
            'height': 200,
        })
        return f'/report/barcode/?{encoded_params}'

    def _is_sales_refund(self):
        self.ensure_one()
        return self.company_id.l10n_jo_edi_taxpayer_type == 'sales' and self.move_type == 'out_refund'

    def _get_invoice_scope_code(self):
        return {
            'local': '0',
            'export': '1',
            'development': '2',
        }.get(self.l10n_jo_edi_invoice_type, '0')

    def _get_invoice_payment_method_code(self):
        return '1' if self.preferred_payment_method_line_id.journal_id.type == 'cash' else '2'

    def _get_invoice_tax_payer_type_code(self):
        return {
            'income': '1',
            'sales': '2',
            'special': '3',
        }.get(self.company_id.l10n_jo_edi_taxpayer_type, '1')

    def button_draft(self):
        # EXTENDS 'account'
        self.write(
            {
                "l10n_jo_edi_error": False,
                "l10n_jo_edi_state": False,
                "l10n_jo_edi_qr": False,
            }
        )
        return super().button_draft()

    def _get_fields_to_detach(self):
        # EXTENDS account
        fields_list = super()._get_fields_to_detach()
        fields_list.append('l10n_jo_edi_xml_attachment_file')
        return fields_list

    def _post(self, soft=True):
        # EXTENDS 'account'
        for invoice in self.filtered('l10n_jo_edi_is_needed'):
            invoice.l10n_jo_edi_state = 'to_send'
        return super()._post(soft)

    def _get_name_invoice_report(self):
        # EXTENDS account
        self.ensure_one()
        if self.l10n_jo_edi_state in ['sent', 'demo'] and self.l10n_jo_edi_xml_attachment_id:
            return 'l10n_jo_edi.report_invoice_document'
        return super()._get_name_invoice_report()

    def _l10n_jo_build_jofotara_headers(self):
        self.ensure_one()
        return {
            'Client-Id': self.sudo().company_id.l10n_jo_edi_client_identifier,
            'Secret-Key': self.sudo().company_id.l10n_jo_edi_secret_key,
        }

    def _send_l10n_jo_edi_request(self, params, headers):
        if self.env.company.l10n_jo_edi_demo_mode:
            return {'EINV_QR': "Demo JoFotara QR"}  # mocked response

        try:
            response = requests.post(JOFOTARA_URL, json=params, headers=headers, timeout=50)
        except requests.exceptions.Timeout:
            return {'error': _("Request timeout! Please try again.")}
        except requests.exceptions.RequestException as e:
            return {'error': _("Invalid request: %s", e)}

        if not response.ok:
            content = response.content.decode()
            if response.status_code == 403:
                content = _("Access forbidden. Please verify your JoFotara credentials.")
            return {'error': _("Request failed: %s", content)}
        dict_response = response.json()
        return dict_response

    def _submit_to_jofotara(self):
        self.ensure_one()
        headers = self._l10n_jo_build_jofotara_headers()
        xml_invoice = self.env['account.edi.xml.ubl_21.jo']._export_invoice(self)[0]
        params = {'invoice': base64.b64encode(xml_invoice).decode()}
        dict_response = self._send_l10n_jo_edi_request(params, headers)
        if 'error' in dict_response and len(dict_response) == 1:
            return dict_response['error']
        self.l10n_jo_edi_qr = str(dict_response.get('EINV_QR', ''))
        self.invoice_pdf_report_id.res_field = False
        self.env["ir.attachment"].create(
            {
                "res_model": "account.move",
                "res_id": self.id,
                "res_field": "l10n_jo_edi_xml_attachment_file",
                "name": self._l10n_jo_edi_get_xml_attachment_name(),
                "raw": xml_invoice,
            }
        )

    def _l10n_jo_edi_get_xml_attachment_name(self):
        return f"{self.name.replace('/', '_')}_edi.xml"

    def _l10n_jo_validate_config(self):
        error_msgs = []
        if not self.sudo().company_id.l10n_jo_edi_client_identifier:
            error_msgs.append(_("Client ID is missing."))
        if not self.sudo().company_id.l10n_jo_edi_secret_key:
            error_msgs.append(_("Secret key is missing."))
        if not self.company_id.l10n_jo_edi_taxpayer_type:
            error_msgs.append(_("Taxpayer type is missing."))
        if not self.company_id.l10n_jo_edi_sequence_income_source:
            error_msgs.append(_("Activity number (Sequence of income source) is missing."))

        if error_msgs:
            return _("%s \nTo set: Configuration > Settings > Electronic Invoicing (Jordan)", "\n".join(error_msgs))

    def _l10n_jo_validate_fields(self):
        def has_non_digit_vat(partner, partner_type, error_msgs):
            if partner.vat and not partner.vat.isdigit():
                error_msgs.append(_("JoFotara portal cannot process %s VAT with non-digit characters in it", partner_type))

        error_msgs = []

        if not self.preferred_payment_method_line_id:
            error_msgs.append(_("Please select a payment method before submission."))
        if not self.l10n_jo_edi_invoice_type:
            error_msgs.append(_("Please select an invoice type before submitting this invoice to JoFotara."))

        customer = self.partner_id
        has_non_digit_vat(customer, 'customer', error_msgs)

        supplier = self.company_id.partner_id.commercial_partner_id
        has_non_digit_vat(supplier, 'supplier', error_msgs)

        if self.move_type == 'out_refund':
            if not self.reversed_entry_id:
                error_msgs.append(_('Please use "Reversal of" to link this credit note with an Invoice'))
            elif self.currency_id != self.reversed_entry_id.currency_id:
                error_msgs.append(_("Please make sure the currency of the credit note is the same as the related invoice"))

            if not self.ref:
                error_msgs.append(_('Please make sure the "Customer Reference" contains the reason for the return'))

        if any(
            line.display_type not in ('line_section', 'line_subsection', 'line_note')
            and (line.quantity < 0 or line.price_unit < 0)
            for line in self.invoice_line_ids
        ):
            error_msgs.append(_("JoFotara portal cannot process negative quantity nor negative price on invoice lines"))

        for line in self.invoice_line_ids.filtered(lambda line: line.display_type not in ('line_section', 'line_subsection', 'line_note')):
            if self.company_id.l10n_jo_edi_taxpayer_type == 'income' and len(line.tax_ids) != 0:
                error_msgs.append(_("No taxes are allowed on invoice lines for taxpayers unregistered in the sales tax"))
            elif self.company_id.l10n_jo_edi_taxpayer_type == 'sales' and len(line.tax_ids) != 1:
                error_msgs.append(_("One general tax per invoice line is expected for taxpayers registered in the sales tax"))
            elif self.company_id.l10n_jo_edi_taxpayer_type == 'special' and len(line.tax_ids) != 2:
                error_msgs.append(_("One special and one general tax per invoice line is expected for taxpayers registered in the special tax"))

        return "\n".join(error_msgs)

    def _mark_sent_jo_edi(self):
        self.l10n_jo_edi_error = False
        self.l10n_jo_edi_state = 'demo' if self.env.company.l10n_jo_edi_demo_mode else 'sent'

    def _l10n_jo_edi_send(self):
        self.ensure_one()
        if not self.env['res.company']._with_locked_records(records=self, allow_raising=False):
            return
        if error_message := self._l10n_jo_validate_config() or self._l10n_jo_validate_fields() or self._submit_to_jofotara():
            self.l10n_jo_edi_error = error_message
            return error_message
        else:
            self._mark_sent_jo_edi()
            self.message_post(
                body=_("E-invoice (JoFotara) submitted successfully."),
                attachment_ids=self.l10n_jo_edi_xml_attachment_id.ids,
            )
