"""
Fusion Accounting - Invoice External Tax Integration
=====================================================

Extends ``account.move`` to support external tax computation through the
:class:`FusionExternalTaxProvider` framework. When enabled for an invoice,
taxes are calculated by the configured external provider (e.g. AvaTax)
instead of using Odoo's built-in tax engine.

Key behaviours:
* Before posting, the external tax provider is called to compute line-level
  taxes. The resulting tax amounts are written to dedicated tax lines on the
  invoice.
* When an invoice is reset to draft, any previously committed external
  transactions are voided so they do not appear in tax filings.
* A status widget on the invoice form indicates whether taxes have been
  computed externally and the associated document code.

Copyright (c) Nexa Systems Inc. - All rights reserved.
"""

import logging

from odoo import api, fields, models, Command, _
from odoo.exceptions import UserError

_logger = logging.getLogger(__name__)


class FusionMoveExternalTax(models.Model):
    """Adds external tax provider support to journal entries / invoices."""

    _inherit = "account.move"

    # -------------------------------------------------------------------------
    # Fields
    # -------------------------------------------------------------------------
    fusion_is_tax_computed_externally = fields.Boolean(
        string="Tax Computed Externally",
        default=False,
        copy=False,
        help="Indicates that the tax amounts on this invoice were calculated "
             "by an external tax provider rather than Odoo's built-in engine.",
    )
    fusion_tax_provider_id = fields.Many2one(
        comodel_name='fusion.external.tax.provider',
        string="External Tax Provider",
        copy=False,
        help="The external tax provider used to compute taxes on this invoice.",
    )
    fusion_external_doc_code = fields.Char(
        string="External Document Code",
        copy=False,
        readonly=True,
        help="Reference code returned by the external tax provider. "
             "Used to void or adjust the transaction.",
    )
    fusion_external_tax_amount = fields.Monetary(
        string="External Tax Amount",
        currency_field='currency_id',
        copy=False,
        readonly=True,
        help="Total tax amount as calculated by the external provider.",
    )
    fusion_external_tax_date = fields.Datetime(
        string="Tax Computation Date",
        copy=False,
        readonly=True,
        help="Timestamp of the most recent external tax computation.",
    )
    fusion_use_external_tax = fields.Boolean(
        string="Use External Tax Provider",
        compute='_compute_fusion_use_external_tax',
        store=False,
        help="Technical field: True when an external provider is active for "
             "this company and the move type supports external taxation.",
    )

    # -------------------------------------------------------------------------
    # Computed Fields
    # -------------------------------------------------------------------------
    @api.depends('company_id', 'move_type')
    def _compute_fusion_use_external_tax(self):
        """Determine whether external tax computation is available."""
        provider_model = self.env['fusion.external.tax.provider']
        for move in self:
            provider = provider_model.get_provider(company=move.company_id)
            move.fusion_use_external_tax = bool(provider) and move.move_type in (
                'out_invoice', 'out_refund', 'in_invoice', 'in_refund',
            )

    # -------------------------------------------------------------------------
    # External Tax Computation
    # -------------------------------------------------------------------------
    def _compute_external_taxes(self):
        """Call the external tax provider and update invoice tax lines.

        For each invoice in the recordset:
        1. Identifies the active external provider.
        2. Sends the product lines to the provider's ``calculate_tax`` method.
        3. Updates or creates tax lines on the invoice to reflect the
           externally computed amounts.
        4. Stores the external document code for later void/adjustment.
        """
        provider_model = self.env['fusion.external.tax.provider']

        for move in self:
            if move.move_type not in ('out_invoice', 'out_refund', 'in_invoice', 'in_refund'):
                continue

            provider = move.fusion_tax_provider_id or provider_model.get_provider(
                company=move.company_id,
            )
            if not provider:
                _logger.info(
                    "No active external tax provider for company %s, skipping move %s.",
                    move.company_id.name, move.name,
                )
                continue

            product_lines = move.invoice_line_ids.filtered(
                lambda l: l.display_type == 'product'
            )
            if not product_lines:
                continue

            _logger.info(
                "Computing external taxes for move %s via provider '%s'.",
                move.name or 'Draft', provider.name,
            )

            try:
                tax_result = provider.calculate_tax(product_lines)
            except UserError:
                raise
            except Exception as exc:
                raise UserError(_(
                    "External tax computation failed for invoice %(ref)s:\n%(error)s",
                    ref=move.name or 'Draft',
                    error=str(exc),
                ))

            # Apply results
            move._apply_external_tax_result(tax_result, provider)

    def _apply_external_tax_result(self, tax_result, provider):
        """Write the external tax computation result onto the invoice.

        Creates or updates a dedicated tax line for the externally computed
        tax amount and records metadata about the computation.

        :param tax_result: ``dict`` returned by ``provider.calculate_tax()``.
        :param provider:   ``fusion.external.tax.provider`` record.
        """
        self.ensure_one()
        doc_code = tax_result.get('doc_code', '')
        total_tax = tax_result.get('total_tax', 0.0)

        # Find or create a dedicated "External Tax" account.tax record
        external_tax = self._get_or_create_external_tax_record(provider)

        # Update per-line tax amounts from provider response
        line_results = {lr['line_id']: lr for lr in tax_result.get('lines', []) if lr.get('line_id')}

        for line in self.invoice_line_ids.filtered(lambda l: l.display_type == 'product'):
            lr = line_results.get(line.id)
            if lr and external_tax:
                # Ensure the external tax is applied to the line
                if external_tax not in line.tax_ids:
                    line.tax_ids = [Command.link(external_tax.id)]

        # Store external tax metadata
        self.write({
            'fusion_is_tax_computed_externally': True,
            'fusion_tax_provider_id': provider.id,
            'fusion_external_doc_code': doc_code,
            'fusion_external_tax_amount': total_tax,
            'fusion_external_tax_date': fields.Datetime.now(),
        })

        _logger.info(
            "External tax applied: move=%s doc_code=%s total_tax=%s",
            self.name, doc_code, total_tax,
        )

    def _get_or_create_external_tax_record(self, provider):
        """Find or create a placeholder ``account.tax`` for external tax lines.

        The placeholder tax record allows the externally computed amount to be
        recorded in the standard tax line infrastructure without conflicting
        with manually configured taxes.

        :param provider: Active ``fusion.external.tax.provider`` record.
        :returns: ``account.tax`` record or ``False``.
        """
        company = self.company_id
        tax_xmlid = f"fusion_accounting.external_tax_{provider.code}_{company.id}"
        existing = self.env.ref(tax_xmlid, raise_if_not_found=False)
        if existing:
            return existing

        # Find a suitable tax account (default tax payable)
        tax_account = (
            company.account_sale_tax_id.invoice_repartition_line_ids
            .filtered(lambda rl: rl.repartition_type == 'tax')[:1]
            .account_id
        )
        if not tax_account:
            # Fall back to searching for a tax payable account
            tax_account = self.env['account.account'].search([
                ('company_ids', 'in', company.id),
                ('account_type', '=', 'liability_current'),
            ], limit=1)

        if not tax_account:
            _logger.warning(
                "No tax account found for external tax placeholder (company=%s). "
                "External tax lines may not be properly recorded.",
                company.name,
            )
            return False

        # Create the placeholder tax
        tax_vals = {
            'name': f"External Tax ({provider.name})",
            'type_tax_use': 'sale',
            'amount_type': 'fixed',
            'amount': 0.0,
            'company_id': company.id,
            'active': True,
            'invoice_repartition_line_ids': [
                Command.create({'repartition_type': 'base', 'factor_percent': 100.0}),
                Command.create({
                    'repartition_type': 'tax',
                    'factor_percent': 100.0,
                    'account_id': tax_account.id,
                }),
            ],
            'refund_repartition_line_ids': [
                Command.create({'repartition_type': 'base', 'factor_percent': 100.0}),
                Command.create({
                    'repartition_type': 'tax',
                    'factor_percent': 100.0,
                    'account_id': tax_account.id,
                }),
            ],
        }

        new_tax = self.env['account.tax'].create(tax_vals)

        # Register under an XML ID for future lookups
        self.env['ir.model.data'].create({
            'name': f"external_tax_{provider.code}_{company.id}",
            'module': 'fusion_accounting',
            'model': 'account.tax',
            'res_id': new_tax.id,
            'noupdate': True,
        })

        return new_tax

    # -------------------------------------------------------------------------
    # Void External Taxes
    # -------------------------------------------------------------------------
    def _void_external_taxes(self):
        """Void previously committed external tax transactions.

        Called when an invoice is reset to draft, ensuring that the tax
        provider marks the corresponding transaction as voided.
        """
        for move in self:
            if not move.fusion_is_tax_computed_externally or not move.fusion_external_doc_code:
                continue

            provider = move.fusion_tax_provider_id
            if not provider:
                _logger.warning(
                    "Cannot void external taxes for move %s: no provider linked.",
                    move.name,
                )
                continue

            doc_type_map = {
                'out_invoice': 'SalesInvoice',
                'out_refund': 'ReturnInvoice',
                'in_invoice': 'PurchaseInvoice',
                'in_refund': 'ReturnInvoice',
            }
            doc_type = doc_type_map.get(move.move_type, 'SalesInvoice')

            try:
                provider.void_transaction(move.fusion_external_doc_code, doc_type=doc_type)
                _logger.info(
                    "Voided external tax transaction: move=%s doc_code=%s",
                    move.name, move.fusion_external_doc_code,
                )
            except UserError as exc:
                _logger.warning(
                    "Failed to void external tax for move %s: %s",
                    move.name, exc,
                )

            move.write({
                'fusion_is_tax_computed_externally': False,
                'fusion_external_doc_code': False,
                'fusion_external_tax_amount': 0.0,
                'fusion_external_tax_date': False,
            })

    # -------------------------------------------------------------------------
    # Post Override
    # -------------------------------------------------------------------------
    def _post(self, soft=True):
        """Compute external taxes before the standard posting workflow.

        Invoices that have an active external tax provider (and have not
        already been computed) will have their taxes calculated via the
        external service prior to validation and posting.
        """
        # Compute external taxes for eligible invoices before posting
        for move in self:
            if (
                move.fusion_use_external_tax
                and not move.fusion_is_tax_computed_externally
                and move.move_type in ('out_invoice', 'out_refund', 'in_invoice', 'in_refund')
            ):
                move._compute_external_taxes()

        return super()._post(soft=soft)

    def button_draft(self):
        """Void external tax transactions when resetting to draft."""
        # Void external taxes before resetting
        moves_with_external = self.filtered('fusion_is_tax_computed_externally')
        if moves_with_external:
            moves_with_external._void_external_taxes()
        return super().button_draft()

    # -------------------------------------------------------------------------
    # Actions
    # -------------------------------------------------------------------------
    def action_compute_external_taxes(self):
        """Manual button action to (re-)compute external taxes on the invoice."""
        for move in self:
            if move.state == 'posted':
                raise UserError(_(
                    "Cannot recompute taxes on posted invoice %(ref)s. "
                    "Reset to draft first.",
                    ref=move.name,
                ))
        self._compute_external_taxes()
        return {
            'type': 'ir.actions.client',
            'tag': 'display_notification',
            'params': {
                'title': _("External Tax Computation"),
                'message': _("Taxes have been computed successfully."),
                'type': 'success',
                'sticky': False,
            },
        }

    def action_void_external_taxes(self):
        """Manual button action to void external taxes on the invoice."""
        self._void_external_taxes()
        return {
            'type': 'ir.actions.client',
            'tag': 'display_notification',
            'params': {
                'title': _("External Tax Void"),
                'message': _("External tax transactions have been voided."),
                'type': 'info',
                'sticky': False,
            },
        }
