import copy
import json
import logging
import re
from difflib import SequenceMatcher
from stdnum.eu.vat import guess_country

from odoo import api, fields, models, Command
from odoo.addons.iap.tools import iap_tools
from odoo.exceptions import AccessError
from odoo.tools import _, float_compare
from odoo.tools.misc import clean_context, formatLang

_logger = logging.getLogger(__name__)

PARTNER_AUTOCOMPLETE_ENDPOINT = 'https://partner-autocomplete.odoo.com'
OCR_VERSION = 122


class AccountInvoiceExtractionWords(models.Model):
    _name = "account.invoice_extract.words"
    _description = "Extracted words from invoice scan"

    invoice_id = fields.Many2one("account.move", required=True, ondelete='cascade', index=True, string="Invoice")
    field = fields.Char()

    ocr_selected = fields.Boolean()
    user_selected = fields.Boolean()
    word_text = fields.Char()
    word_page = fields.Integer()
    word_box_midX = fields.Float()
    word_box_midY = fields.Float()
    word_box_width = fields.Float()
    word_box_height = fields.Float()
    word_box_angle = fields.Float()


class AccountMove(models.Model):
    _name = 'account.move'
    _inherit = ['extract.mixin', 'account.move']

    @api.depends('state')
    def _compute_is_in_extractable_state(self):
        for record in self:
            record.is_in_extractable_state = record.state == 'draft' and record.is_invoice()

    @api.depends(
        'state',
        'extract_state',
        'move_type',
        'company_id.extract_in_invoice_digitalization_mode',
        'company_id.extract_out_invoice_digitalization_mode',
    )
    def _compute_show_banners(self):
        for record in self:
            record.extract_can_show_banners = (
                record.state == 'draft' and
                (
                    (record.is_purchase_document() and record.company_id.extract_in_invoice_digitalization_mode != 'no_send') or
                    (record.is_sale_document() and record.company_id.extract_out_invoice_digitalization_mode != 'no_send')
                )
            )

    extract_prefill_data = fields.Json()
    extract_word_ids = fields.One2many("account.invoice_extract.words", inverse_name="invoice_id", copy=False)
    extract_attachment_id = fields.Many2one('ir.attachment', readonly=True, ondelete='set null', copy=False, index='btree_not_null')
    extract_can_show_banners = fields.Boolean("Can show the ocr banners", compute=_compute_show_banners)

    extract_detected_layout = fields.Integer("Extract Detected Layout Id", readonly=True)
    extract_partner_name = fields.Char("Extract Detected Partner Name", readonly=True)

    def action_reload_ai_data(self):
        self = self.with_context(skip_is_manually_modified=True)
        try:
            with self._get_edi_creation() as move_form:
                move_form.partner_id = False
                move_form.invoice_date = False
                move_form.invoice_payment_term_id = False
                move_form.invoice_date_due = False

                if move_form.is_purchase_document():
                    move_form.ref = False
                elif move_form.is_sale_document() and move_form.quick_edit_mode:
                    move_form.name = False

                move_form.payment_reference = False
                move_form.currency_id = move_form.company_currency_id
                move_form.invoice_line_ids = [Command.clear()]
            self._check_ocr_status()
        except Exception as e:
            _logger.warning("Error while reloading AI data on account.move %d: %s", self.id, e)
            raise AccessError(self.env._("Couldn't reload AI data."))

    @api.model
    def _contact_iap_extract(self, pathinfo, params):
        params['version'] = OCR_VERSION
        params['account_token'] = self._get_iap_account().account_token
        endpoint = self.env['ir.config_parameter'].sudo().get_param('iap_extract_endpoint', 'https://extract.api.odoo.com')
        return iap_tools.iap_jsonrpc(endpoint + '/api/extract/invoice/2/' + pathinfo, params=params)

    @api.model
    def _contact_iap_partner_autocomplete(self, local_endpoint, params):
        return iap_tools.iap_jsonrpc(PARTNER_AUTOCOMPLETE_ENDPOINT + local_endpoint, params=params)

    def _check_digitalization_mode(self, company, document_type, mode):
        if document_type in self.get_purchase_types():
            return company.extract_in_invoice_digitalization_mode == mode
        elif document_type in self.get_sale_types():
            return company.extract_out_invoice_digitalization_mode == mode

    def _needs_auto_extract(self, new_document=False):
        self.ensure_one()

        if (
            self.extract_state != "no_extract_requested"
            or not self._check_digitalization_mode(self.company_id, self.move_type, 'auto_send')
            or not self.is_in_extractable_state
        ):
            return False

        if new_document:
            return True

        return self.is_purchase_document()

    def _get_ocr_module_name(self):
        return 'odex30_account_invoice_extract'

    def _get_ocr_option_can_extract(self):
        self.ensure_one()
        return not self._check_digitalization_mode(self.company_id, self.move_type, 'no_send')

    def _get_validation_domain(self):
        base_domain = super()._get_validation_domain()
        return base_domain + [('state', '=', 'posted')]

    def _get_validation_fields(self):
        return [
            'total', 'subtotal', 'total_tax_amount', 'date', 'due_date', 'invoice_id', 'partner',
            'VAT_Number', 'currency', 'payment_ref', 'iban', 'SWIFT_code', 'merged_lines', 'invoice_lines',
        ]

    def _get_user_error_invalid_state_message(self):
        return _("You cannot send a expense that is not in draft state!")

    def _upload_to_extract_success_callback(self):
        super()._upload_to_extract_success_callback()
        self.extract_attachment_id = self.message_main_attachment_id

    def is_indian_taxes(self):
        l10n_in = self.env['ir.module.module'].sudo().search([('name', '=', 'l10n_in')])
        return self.company_id.country_id.code == "IN" and l10n_in and l10n_in.state == 'installed'

    def _get_user_infos(self):
        user_infos = super()._get_user_infos()
        user_infos.update({
            'user_company_VAT': self.company_id.vat,
            'user_company_name': self.company_id.name,
            'user_company_country_code': self.company_id.country_id.code,
            'perspective': 'supplier' if self.is_sale_document() else 'client',
        })
        return user_infos

    def _upload_to_extract(self):
        self.ensure_one()
        if self.is_invoice():
            super()._upload_to_extract()

    def _get_validation(self, field):

        text_to_send = {}
        if field == "total":
            text_to_send["content"] = self.amount_total
        elif field == "subtotal":
            text_to_send["content"] = self.amount_untaxed
        elif field == "total_tax_amount":
            text_to_send["content"] = self.amount_tax
        elif field == "date":
            text_to_send["content"] = str(self.invoice_date) if self.invoice_date else False
        elif field == "due_date":
            text_to_send["content"] = str(self.invoice_date_due) if self.invoice_date_due else False
        elif field == "invoice_id":
            if self.is_purchase_document():
                text_to_send["content"] = self.ref
            else:
                text_to_send["content"] = self.name
        elif field == "partner":
            text_to_send["content"] = self.partner_id.name
        elif field == "VAT_Number":
            text_to_send["content"] = self.partner_id.vat
        elif field == "currency":
            text_to_send["content"] = self.currency_id.name
        elif field == "payment_ref":
            text_to_send["content"] = self.payment_reference
        elif field == "iban":
            text_to_send["content"] = self.partner_bank_id.acc_number if self.partner_bank_id else False
        elif field == "SWIFT_code":
            text_to_send["content"] = self.partner_bank_id.bank_bic if self.partner_bank_id else False
        elif field == 'merged_lines':
            return self.env.company.extract_single_line_per_tax
        elif field == "invoice_lines":
            text_to_send = {'lines': []}
            for il in self.invoice_line_ids:
                line = {
                    "description": il.name,
                    "quantity": il.quantity,
                    "unit_price": il.price_unit,
                    "product": il.product_id.id,
                    "taxes_amount": round(il.price_total - il.price_subtotal, 2),
                    "taxes": [{
                        'amount': tax.amount,
                        'type': tax.amount_type,
                        'price_include': tax.price_include} for tax in il.tax_ids],
                    "subtotal": il.price_subtotal,
                    "total": il.price_total
                }
                text_to_send['lines'].append(line)
            if self.is_indian_taxes():
                lines = text_to_send['lines']
                for index, line in enumerate(text_to_send['lines']):
                    for tax in line['taxes']:
                        taxes = []
                        if tax['type'] == 'group':
                            taxes.extend([{
                                'amount': tax['amount'] / 2,
                                'type': 'percent',
                                'price_include': tax['price_include']
                            } for _ in range(2)])
                        else:
                            taxes.append(tax)
                        lines[index]['taxes'] = taxes
                text_to_send['lines'] = lines
        else:
            return None

        user_selected_box = self.env['account.invoice_extract.words'].search([
            ('invoice_id', '=', self.id),
            ('field', '=', field),
            ('user_selected', '=', True),
            ('ocr_selected', '=', False),
        ])
        if user_selected_box and user_selected_box.word_text == text_to_send['content']:
            text_to_send['box'] = [
                user_selected_box.word_text,
                user_selected_box.word_page,
                user_selected_box.word_box_midX,
                user_selected_box.word_box_midY,
                user_selected_box.word_box_width,
                user_selected_box.word_box_height,
                user_selected_box.word_box_angle,
            ]
        return text_to_send

    @api.model
    def _cron_validate(self):
        validated = super()._cron_validate()
        validated.mapped('extract_word_ids').unlink()
        for record in validated:
            record.extract_prefill_data = None
        return validated

    def _post(self, soft=True):
        posted = super()._post(soft)
        self.with_context(skip_is_manually_modified=True)._validate_ocr()
        return posted

    def get_boxes(self):
        return [{
            "id": data.id,
            "feature": data.field,
            "text": data.word_text,
            "ocr_selected": data.ocr_selected,
            "user_selected": data.user_selected,
            "page": data.word_page,
            "box_midX": data.word_box_midX,
            "box_midY": data.word_box_midY,
            "box_width": data.word_box_width,
            "box_height": data.word_box_height,
            "box_angle": data.word_box_angle} for data in self.extract_word_ids]

    def get_partner_create_data(self, context):
        default_values = self.extract_prefill_data
        if values := self._fetch_autocomplete_values(context.get('default_vat') or default_values.get('vat')):
            default_values |= values
        return {f'default_{k}': v for k, v in default_values.items()}

    def set_user_selected_box(self, id):

        self.ensure_one()
        word = self.env["account.invoice_extract.words"].browse(int(id))
        to_unselect = self.env["account.invoice_extract.words"].search([("invoice_id", "=", self.id), ("field", "=", word.field), ("user_selected", "=", True)])
        for box in to_unselect:
            box.user_selected = False

        word.user_selected = True
        if word.field == "currency":
            text = word.word_text
            currency = None
            currencies = self.env["res.currency"].search([])
            for curr in currencies:
                if text == curr.currency_unit_label:
                    currency = curr
                if text == curr.name or text == curr.symbol:
                    currency = curr
            if currency:
                return currency.id
            return self.currency_id.id
        if word.field == "VAT_Number":
            partner_vat = False
            if word.word_text != "":
                partner_vat = self._find_partner_id_with_vat(word.word_text)
            if partner_vat:
                return partner_vat.id
            else:
                vat = word.word_text
                partner = self._create_supplier_from_vat(vat)
                if partner and self.is_purchase_document():
                    self.partner_id = partner
                return [partner.id, self.partner_bank_id.id] if partner else False

        if word.field == "supplier":
            return self._find_partner_id_with_name(word.word_text)
        return word.word_text

    def _find_partner_from_previous_extracts(self):

        match_conditions = [
            ('extract_detected_layout', '=', self.extract_detected_layout),
            ('extract_partner_name', '=', self.extract_partner_name),
        ]
        for condition in match_conditions:
            invoice_layout = self.search([
                condition,
                ('extract_state', '=', 'done'),
                ('move_type', '=', self.move_type),
                ('company_id', '=', self.company_id.id),
            ], limit=1000, order='id desc')
            if invoice_layout:
                break

        if len(invoice_layout.mapped('partner_id')) == 1:
            return invoice_layout.partner_id
        return None

    def _find_partner_id_with_vat(self, vat_number_ocr):
        rank_field = 'supplier_rank' if self.is_purchase_document() else 'customer_rank'
        partner_vat = self.env["res.partner"].search([
            *self.env['res.partner']._check_company_domain(self.company_id),
            ("vat", "=ilike", vat_number_ocr),
        ], order=f'{rank_field} desc', limit=1)
        if not partner_vat:
            partner_vat = self.env["res.partner"].search([
                *self.env['res.partner']._check_company_domain(self.company_id),
                ("vat", "=ilike", vat_number_ocr[2:]),
            ], order=f'{rank_field} desc', limit=1)
        if not partner_vat:
            for partner in self.env["res.partner"].search([
                *self.env['res.partner']._check_company_domain(self.company_id),
                ("vat", "!=", False),
            ], order=f'{rank_field} desc', limit=1000):
                vat = partner.vat.upper()
                vat_cleaned = vat.replace("BTW", "").replace("MWST", "").replace("ABN", "")
                vat_cleaned = re.sub(r'[^A-Z0-9]', '', vat_cleaned)
                if vat_cleaned == vat_number_ocr or vat_cleaned == vat_number_ocr[2:]:
                    partner_vat = partner
                    break
        return partner_vat

    def _fetch_autocomplete_values(self, vat_number):
        if not vat_number:
            return None

        try:
            response, error = self.env['iap.autocomplete.api']._request_partner_autocomplete('enrich_by_vat', {
                'vat': vat_number,
            })
            if error:
                raise Exception(error)
            if 'credit_error' in response and response['credit_error']:
                _logger.warning("Credit error on partner_autocomplete call")
        except KeyError:
            _logger.warning("Partner autocomplete isn't installed, supplier creation from VAT is disabled")
            return None
        except Exception as exception:
            _logger.error('Check VAT error: %s' % str(exception))
            return None

        if response and response.get('data'):
            country_id = self.env['res.country'].search([('code', '=', response.get('data').get('country_code',''))])
            resp_values = response.get('data')

            values = {field: resp_values[field] for field in ('name', 'vat', 'street', 'city', 'zip', 'phone', 'email') if field in resp_values}
            values['is_company'] = True

            if country_id:
                values['country_id'] = country_id.id
                state_id = self.env['res.country.state'].search([
                    ('name', '=', response.get('data').get('state_name','')),
                    ('country_id', '=', country_id.id),
                ], limit = 1)
                if state_id:
                    values['state_id'] = state_id.id
            return values
        return None

    def _create_supplier_from_vat(self, vat_number_ocr):
        values = self._fetch_autocomplete_values(vat_number_ocr)
        if not values:
            return False

        for field, val in (self.extract_prefill_data or {}).items():
            if field not in values:
                values[field] = val
        return self.env["res.partner"].with_context(clean_context(self.env.context)).create(values)

    def _find_partner_id_with_name(self, partner_name):
        if not partner_name:
            return 0

        rank_field = 'supplier_rank' if self.is_purchase_document() else 'customer_rank'
        partner = self.env["res.partner"].search([
            *self.env['res.partner']._check_company_domain(self.company_id),
            ("name", "=", partner_name),
        ], order=f'{rank_field} desc', limit=1)
        if partner:
            return partner.id if partner.id != self.company_id.partner_id.id else 0

        self.env.cr.execute(*self.env['res.partner']._where_calc([
            *self.env['res.partner']._check_company_domain(self.company_id),
            ('active', '=', True),
            ('name', '!=', False),
            (rank_field, '>', 0),
        ]).select('res_partner.id', 'res_partner.name'))

        partners_dict = {name.lower().replace('-', ' '): partner_id for partner_id, name in self.env.cr.fetchall()}
        partner_name = partner_name.lower().strip()

        partners = {}
        for single_word in [word for word in re.findall(r"\w+", partner_name) if len(word) >= 3]:
            partners_matched = [partner for partner in partners_dict if single_word in partner.split()]
            for partner in partners_matched:
                # Record only if the whole sequence is a very close match
                if SequenceMatcher(None, partner.lower(), partner_name.lower()).ratio() > 0.8:
                    partners[partner] = partners[partner] + 1 if partner in partners else 1

        if partners:
            sorted_partners = sorted(partners, key=partners.get, reverse=True)
            if len(sorted_partners) == 1 or partners[sorted_partners[0]] != partners[sorted_partners[1]]:
                partner = sorted_partners[0]
                if partners_dict[partner] != self.company_id.partner_id.id:
                    return partners_dict[partner]
        return 0

    def _find_partner_with_iban(self, iban_ocr, partner_name):
        bank_accounts = self.env['res.partner.bank'].search([
            *self.env['res.partner.bank']._check_company_domain(self.company_id),
            ('acc_number', '=ilike', iban_ocr),
        ])

        bank_account_match_ratios = sorted([
            (account, SequenceMatcher(None, partner_name.lower(), account.partner_id.name.lower()).ratio())
            for account in bank_accounts
        ], key=lambda x: x[1], reverse=True)

        if bank_account_match_ratios and bank_account_match_ratios[0][1] > 0.3:
            return bank_account_match_ratios[0][0].partner_id
        return None

    def _get_partner(self, ocr_results):
        vat_number_ocr = self._get_ocr_selected_value(ocr_results, 'VAT_Number', "")
        iban_ocr = self._get_ocr_selected_value(ocr_results, 'iban', "")

        if vat_number_ocr:
            partner_vat = self._find_partner_id_with_vat(vat_number_ocr)
            if partner_vat:
                return partner_vat, False

        if self.is_purchase_document() and self.extract_detected_layout:
            partner = self._find_partner_from_previous_extracts()
            if partner:
                return partner, False

        if self.is_purchase_document() and iban_ocr:
            partner = self._find_partner_with_iban(iban_ocr, self.extract_partner_name)
            if partner:
                return partner, False

        partner_id = self._find_partner_id_with_name(self.extract_partner_name)
        if partner_id != 0:
            return self.env["res.partner"].browse(partner_id), False

        if vat_number_ocr:
            created_supplier = self._create_supplier_from_vat(vat_number_ocr)
            if created_supplier:
                return created_supplier, True
        return False, False

    def _get_taxes_record(self, taxes_ocr, taxes_type_ocr):

        taxes_found = self.env['account.tax']
        type_tax_use = 'purchase' if self.is_purchase_document() else 'sale'
        if self.is_indian_taxes() and len(taxes_ocr) > 1:
            total_tax = sum(taxes_ocr)
            grouped_taxes_records = self.env['account.tax'].search([
                *self.env['account.tax']._check_company_domain(self.company_id),
                ('amount', '=', total_tax),
                ('amount_type', '=', 'group'),
                ('type_tax_use', '=', type_tax_use),
            ])
            for grouped_tax in grouped_taxes_records:
                children_taxes = grouped_tax.children_tax_ids.mapped('amount')
                if set(taxes_ocr) == set(children_taxes):
                    return grouped_tax
        for (taxes, taxes_type) in zip(taxes_ocr, taxes_type_ocr):
            if taxes != 0.0:
                related_documents = self.env['account.move'].search([
                    ('state', '!=', 'draft'),
                    ('move_type', '=', self.move_type),
                    ('partner_id', '=', self.partner_id.id),
                    ('company_id', '=', self.company_id.id),
                ], limit=100, order='id desc')
                lines = related_documents.mapped('invoice_line_ids')
                taxes_ids = related_documents.mapped('invoice_line_ids.tax_ids')
                taxes_ids = taxes_ids.filtered(
                    lambda tax:
                        tax.active and
                        tax.amount == taxes and
                        tax.amount_type == taxes_type and
                        tax.type_tax_use == type_tax_use
                )
                taxes_by_document = []
                for tax in taxes_ids:
                    taxes_by_document.append((tax, lines.filtered(lambda line: tax in line.tax_ids)))
                if len(taxes_by_document) != 0:
                    taxes_found |= max(taxes_by_document, key=lambda tax: len(tax[1]))[0]
                else:
                    tax_domain = [
                        *self.env['account.tax']._check_company_domain(self.company_id),
                        ('amount', '=', taxes),
                        ('amount_type', '=', taxes_type),
                        ('type_tax_use', '=', type_tax_use),
                    ]
                    default_taxes = self.journal_id.default_account_id.tax_ids
                    matching_default_tax = default_taxes.filtered_domain(tax_domain)
                    if matching_default_tax:
                        taxes_found |= matching_default_tax
                    else:
                        taxes_records = self.env['account.tax'].search(tax_domain)
                        if taxes_records:
                            taxes_records_setting_based = taxes_records.filtered(lambda r: not r.price_include)
                            if taxes_records_setting_based:
                                taxes_record = taxes_records_setting_based[0]
                            else:
                                taxes_record = taxes_records[0]
                            taxes_found |= taxes_record
        return taxes_found

    def _get_currency(self, currency_ocr, partner_id):
        for comparison in ['=ilike', 'ilike']:
            possible_currencies = self.env["res.currency"].search([
                '|', '|',
                ('currency_unit_label', comparison, currency_ocr),
                ('name', comparison, currency_ocr),
                ('symbol', comparison, currency_ocr),
            ])
            if possible_currencies:
                break

        partner_last_invoice_currency = partner_id.invoice_ids[:1].currency_id
        if partner_last_invoice_currency in possible_currencies:
            return partner_last_invoice_currency
        if self.company_id.currency_id in possible_currencies:
            return self.company_id.currency_id
        return possible_currencies if len(possible_currencies) == 1 else None


    def _get_invoice_lines(self, ocr_results):

        self.ensure_one()

        invoice_lines = ocr_results.get('invoice_lines', [])
        subtotal_ocr = self._get_ocr_selected_value(ocr_results, 'subtotal', 0.0)
        supplier_ocr = self._get_ocr_selected_value(ocr_results, 'supplier', "")
        date_ocr = self._get_ocr_selected_value(ocr_results, 'date', "")

        invoice_lines_to_create = []
        if self.company_id.extract_single_line_per_tax:
            merged_lines = {}
            for il in invoice_lines:
                total = self._get_ocr_selected_value(il, 'total', 0.0)
                subtotal = self._get_ocr_selected_value(il, 'subtotal', total)
                taxes_ocr = [value['content'] for value in il.get('taxes', {}).get('selected_values', [])]
                taxes_type_ocr = [value.get('amount_type', 'percent') for value in il.get('taxes', {}).get('selected_values', [])]
                taxes_records = self._get_taxes_record(taxes_ocr, taxes_type_ocr)

                if not taxes_records and taxes_ocr:
                    taxes_ids = ('not found', *sorted(taxes_ocr))
                else:
                    taxes_ids = ('found', *sorted(taxes_records.ids))

                if taxes_ids not in merged_lines:
                    merged_lines[taxes_ids] = {'subtotal': subtotal}
                else:
                    merged_lines[taxes_ids]['subtotal'] += subtotal
                merged_lines[taxes_ids]['taxes_records'] = taxes_records

            if len(merged_lines) == 1:
                merged_lines[list(merged_lines.keys())[0]]['subtotal'] = subtotal_ocr

            description_fields = []
            if supplier_ocr:
                description_fields.append(supplier_ocr)
            if date_ocr:
                description_fields.append(date_ocr.split()[0])
            description = ' - '.join(description_fields)

            for il in merged_lines.values():
                vals = {
                    'name': description,
                    'price_unit': il['subtotal'],
                    'quantity': 1.0,
                    'tax_ids': il['taxes_records'],
                }

                invoice_lines_to_create.append(vals)
        else:
            for il in invoice_lines:
                description = self._get_ocr_selected_value(il, 'description', "/")
                total = self._get_ocr_selected_value(il, 'total', 0.0)
                subtotal = self._get_ocr_selected_value(il, 'subtotal', total)
                unit_price = self._get_ocr_selected_value(il, 'unit_price', subtotal)
                quantity = self._get_ocr_selected_value(il, 'quantity', 1.0)
                taxes_ocr = [value['content'] for value in il.get('taxes', {}).get('selected_values', [])]
                taxes_type_ocr = [value.get('amount_type', 'percent') for value in il.get('taxes', {}).get('selected_values', [])]

                vals = {
                    'name': description,
                    'price_unit': unit_price,
                    'quantity': quantity,
                    'tax_ids': self._get_taxes_record(taxes_ocr, taxes_type_ocr)
                }

                invoice_lines_to_create.append(vals)

        return invoice_lines_to_create

    def _get_bank_account_vals(self, iban_ocr, SWIFT_code_ocr):
        vals = {'acc_number': iban_ocr}
        if SWIFT_code_ocr:
            bank_id = self.env['res.bank'].search([('bic', '=', SWIFT_code_ocr['bic'])], limit=1)
            if bank_id:
                vals['bank_id'] = bank_id.id
            if not bank_id and SWIFT_code_ocr['verified_bic']:
                country_id = self.env['res.country'].search([('code', '=', SWIFT_code_ocr['country_code'])], limit=1)
                if country_id:
                    vals['bank_id'] = self.env['res.bank'].create({
                        'name': SWIFT_code_ocr['name'],
                        'country': country_id.id,
                        'city': SWIFT_code_ocr['city'],
                        'bic': SWIFT_code_ocr['bic'],
                    }).id
        return vals

    def _fill_document_with_results(self, ocr_results):
        self = self.with_context(skip_is_manually_modified=True)
        if self.state != 'draft' or ocr_results is None:
            return

        if 'detected_layout_id' in ocr_results:
            self.extract_detected_layout = ocr_results['detected_layout_id']

        if ocr_results.get('type') == 'refund' and self.move_type in ('in_invoice', 'out_invoice'):
            self.action_switch_move_type()

        def get_first_value_without(feature, not_allowed):
            return next((
                candidate['content']
                for candidate in ocr_results.get(feature, {}).get('candidates', [])
                if candidate['content'] not in not_allowed
            ), None)

        country_code = self._get_ocr_selected_value(ocr_results, 'country', "")
        iban_ocr = self._get_ocr_selected_value(ocr_results, 'iban', "")
        SWIFT_code_ocr = json.loads(self._get_ocr_selected_value(ocr_results, 'SWIFT_code', "{}"))
        self.extract_prefill_data = {
            'country_id': self.env['res.country'].search([('code', '=', country_code)], limit=1).id,
            'email': get_first_value_without('email', (self.company_id.email,)),
            'website': get_first_value_without('website', (self.company_id.website,)),
            'phone': get_first_value_without('phone', (self.company_id.phone,)),
            'mobile': get_first_value_without('mobile', (self.company_id.mobile,)),
            'vat': next((
                    candidate['content']
                    for candidate in ocr_results.get('VAT_Number', {}).get('candidates', [])
                    if next(iter(guess_country(candidate['content'])), "").lower() == country_code.lower()
                ), next(iter(ocr_results.get('VAT_Number', {}).get('candidates', [])), {}).get('content')),
        }
        self.extract_prefill_data = {k: v for k, v in self.extract_prefill_data.items() if v is not None}

        self._save_form(ocr_results)

        if self.extract_word_ids:
            return

        fields_with_boxes = ['supplier', 'date', 'due_date', 'invoice_id', 'currency', 'VAT_Number', 'total']
        for field in filter(ocr_results.get, fields_with_boxes):
            value = ocr_results[field]
            selected_value = value.get('selected_value')
            data = []

            ocr_chosen_candidate_found = False
            for candidate in value.get('candidates', []):
                ocr_chosen = selected_value == candidate and not ocr_chosen_candidate_found
                if ocr_chosen:
                    ocr_chosen_candidate_found = True
                data.append((0, 0, {
                    "field": field,
                    "ocr_selected": ocr_chosen,
                    "user_selected": ocr_chosen,
                    "word_text": candidate['content'],
                    "word_page": candidate['page'],
                    "word_box_midX": candidate['coords'][0],
                    "word_box_midY": candidate['coords'][1],
                    "word_box_width": candidate['coords'][2],
                    "word_box_height": candidate['coords'][3],
                    "word_box_angle": candidate['coords'][4],
                }))
            self.write({'extract_word_ids': data})
        self._autopost_bill()

    def _save_form(self, ocr_results):
        self = self.with_context(skip_is_manually_modified=True)

        date_ocr = self._get_ocr_selected_value(ocr_results, 'date', "")
        due_date_ocr = self._get_ocr_selected_value(ocr_results, 'due_date', "")
        total_ocr = self._get_ocr_selected_value(ocr_results, 'total', 0.0)
        invoice_id_ocr = self._get_ocr_selected_value(ocr_results, 'invoice_id', "")
        currency_ocr = self._get_ocr_selected_value(ocr_results, 'currency', "")
        payment_ref_ocr = self._get_ocr_selected_value(ocr_results, 'payment_ref', "")
        iban_ocr = self._get_ocr_selected_value(ocr_results, 'iban', "")
        SWIFT_code_ocr = json.loads(self._get_ocr_selected_value(ocr_results, 'SWIFT_code', "{}")) or None
        qr_bill_ocr = self._get_ocr_selected_value(ocr_results, 'qr-bill')
        supplier_ocr = self._get_ocr_selected_value(ocr_results, 'supplier', "")
        client_ocr = self._get_ocr_selected_value(ocr_results, 'client', "")
        total_tax_amount_ocr = self._get_ocr_selected_value(ocr_results, 'total_tax_amount', 0.0)

        self.extract_partner_name = client_ocr if self.is_sale_document() else supplier_ocr

        with self._get_edi_creation() as move_form:
            if not move_form.partner_id:
                partner_id, created = self._get_partner(ocr_results)
                if partner_id:
                    move_form.partner_id = partner_id
                    if created and iban_ocr and not move_form.partner_bank_id and self.is_purchase_document():
                        bank_account = self.env['res.partner.bank'].search([
                            *self.env['res.partner.bank']._check_company_domain(self.company_id),
                            ('acc_number', '=ilike', iban_ocr),
                        ])
                        if bank_account:
                            if bank_account.partner_id == move_form.partner_id.id:
                                move_form.partner_bank_id = bank_account
                        else:
                            bank_vals = self._get_bank_account_vals(iban_ocr, SWIFT_code_ocr)
                            bank_vals['partner_id'] = move_form.partner_id.id
                            move_form.partner_bank_id = self.with_context(clean_context(self.env.context)).env['res.partner.bank'].create(bank_vals)

            if qr_bill_ocr:
                qr_content_list = qr_bill_ocr.splitlines()
                index_offset = 16 if self.is_sale_document() else 0
                if not move_form.partner_id:
                    partner_name = qr_content_list[5 + index_offset]
                    move_form.partner_id = self.env["res.partner"].with_context(clean_context(self.env.context)).create({
                        'name': partner_name,
                        'is_company': True,
                    })

                partner = move_form.partner_id
                address_type = qr_content_list[4 + index_offset]
                if address_type == 'S':
                    if not partner.street:
                        street = qr_content_list[6 + index_offset]
                        house_nb = qr_content_list[7 + index_offset]
                        partner.street = " ".join((street, house_nb))

                    if not partner.zip:
                        partner.zip = qr_content_list[8 + index_offset]

                    if not partner.city:
                        partner.city = qr_content_list[9 + index_offset]

                elif address_type == 'K':
                    if not partner.street:
                        partner.street = qr_content_list[6 + index_offset]
                        partner.street2 = qr_content_list[7 + index_offset]

                country_code = qr_content_list[10 + index_offset]
                if not partner.country_id and country_code:
                    country = self.env['res.country'].search([('code', '=', country_code)])
                    partner.country_id = country and country.id

                if self.is_purchase_document():
                    iban = qr_content_list[3]
                    if iban and not self.env['res.partner.bank'].search_count([('acc_number', '=ilike', iban)], limit=1):
                        move_form.partner_bank_id = self.with_context(clean_context(self.env.context)).env['res.partner.bank'].create({
                            'acc_number': iban,
                            'company_id': move_form.company_id.id,
                            'currency_id': move_form.currency_id.id,
                            'partner_id': partner.id,
                        })

            due_date_move_form = move_form.invoice_date_due
            context_create_date = fields.Date.context_today(self, self.create_date)
            if date_ocr and (not move_form.invoice_date or move_form.invoice_date == context_create_date):
                move_form.invoice_date = date_ocr
            if due_date_ocr and due_date_move_form == context_create_date:
                if date_ocr == due_date_ocr and move_form.partner_id and move_form.partner_id.property_supplier_payment_term_id:
                    move_form.invoice_payment_term_id = move_form.partner_id.property_supplier_payment_term_id
                else:
                    move_form.invoice_date_due = due_date_ocr

            if self.is_purchase_document() and not move_form.ref:
                move_form.ref = invoice_id_ocr

            if self.is_sale_document() and self.quick_edit_mode:
                move_form.name = invoice_id_ocr

            if payment_ref_ocr and not move_form.payment_reference:
                move_form.payment_reference = payment_ref_ocr

            add_lines = not move_form.invoice_line_ids
            if add_lines:
                if currency_ocr and move_form.currency_id == move_form.company_currency_id:
                    currency = self._get_currency(currency_ocr, move_form.partner_id)
                    if currency:
                        move_form.currency_id = currency

                vals_invoice_lines = self._get_invoice_lines(ocr_results)
                move_form.invoice_line_ids = [
                    Command.create({'name': line_vals.pop('name')})
                    for line_vals in vals_invoice_lines
                ]

        if add_lines:
            with self._get_edi_creation() as move_form:
                for line, ocr_line_vals in zip(move_form.invoice_line_ids[-len(vals_invoice_lines):], vals_invoice_lines):
                    line.write({
                        'price_unit': ocr_line_vals['price_unit'],
                        'quantity': ocr_line_vals['quantity'],
                    })
                    taxes_dict = {}
                    for tax in line.tax_ids:
                        taxes_dict[(tax.amount, tax.amount_type, tax.price_include)] = {
                            'found_by_OCR': False,
                            'tax_record': tax,
                        }
                    for taxes_record in ocr_line_vals['tax_ids']:
                        tax_tuple = (taxes_record.amount, taxes_record.amount_type, taxes_record.price_include)
                        if tax_tuple not in taxes_dict:
                            line.tax_ids = [Command.link(taxes_record.id)]
                        else:
                            taxes_dict[tax_tuple]['found_by_OCR'] = True
                        if taxes_record.price_include:
                            line.price_unit *= 1 + taxes_record.amount / 100
                    for tax_info in taxes_dict.values():
                        if not tax_info['found_by_OCR']:
                            amount_before = line.price_total
                            line.tax_ids = [Command.unlink(tax_info['tax_record'].id)]

                            if line.price_total == amount_before:
                                line.tax_ids = [Command.link(tax_info['tax_record'].id)]

            tax_amount_rounding_error = total_ocr - self.tax_totals['total_amount_currency']
            threshold = len(vals_invoice_lines) * move_form.currency_id.rounding
            if not move_form.currency_id.is_zero(tax_amount_rounding_error) and self.is_indian_taxes():
                fixed_rounding_error = total_ocr - total_tax_amount_ocr - self.tax_totals['base_amount_currency']
                tax_totals = self.tax_totals
                if move_form.currency_id.is_zero(fixed_rounding_error) and tax_totals['has_tax_groups']:
                    max_tax_group = max(
                        [
                            tax_group
                            for subtotal in tax_totals['subtotals']
                            for tax_group in subtotal['tax_groups']
                        ],
                        key=lambda tax_group: tax_group['tax_amount_currency'],
                    )
                    max_tax_group['tax_amount_currency'] += fixed_rounding_error
                self.tax_totals = tax_totals

            move_form.invoice_line_ids.is_imported = True

            if (
                not move_form.currency_id.is_zero(tax_amount_rounding_error) and
                float_compare(abs(tax_amount_rounding_error), threshold, precision_digits=2) <= 0
            ):
                self._check_total_amount(total_ocr)


    @api.model
    def _import_invoice_ocr(self, invoice, file_data, new=False):
        with invoice._get_edi_creation() as invoice:
            invoice._message_set_main_attachment_id(file_data['attachment'], force=True, filter_xml=False)
            invoice._send_batch_for_digitization()
        return True

    def _get_edi_decoder(self, file_data, new=False):
        self.ensure_one()
        decoder = super()._get_edi_decoder(file_data, new=new)
        if not decoder and file_data['type'] in ('pdf', 'binary') and self._needs_auto_extract(new_document=new):
            return self._import_invoice_ocr
        return super()._get_edi_decoder(file_data, new=new)

    @api.model
    def _get_view(self, view_id=None, view_type='form', **options):
        arch, view = super()._get_view(view_id, view_type, **options)

        if view_type == 'form':
            for node in arch.xpath("//field[@name='partner_id'][@widget='res_partner_many2one']"):

                node_with_placeholder = copy.deepcopy(node)
                placeholder_condition = "(extract_state == 'waiting_validation' and not partner_id)"
                if node.get("invisible"):
                    node.set('invisible', f"{placeholder_condition} or ({node.attrib.pop('invisible')})")
                else:
                    node.set('invisible', placeholder_condition)
                node_with_placeholder.set('invisible', f"not {node.get('invisible')}")
                node_with_placeholder.set('placeholder', "Click here and select the vendor on the bill to create it")
                node.addnext(node_with_placeholder)
        return arch, view
