"""
Fusion Accounting - Extraction Review Wizard

Transient model that presents the OCR-extracted invoice fields alongside
the original scan so the user can verify, correct, and then apply them
to the parent ``account.move`` record.

Corrections are tracked so they can later feed back into extraction
quality metrics or fine-tuning data sets.

Original implementation by Nexa Systems Inc.
"""

import json
import logging

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

_log = logging.getLogger(__name__)


class FusionExtractionReviewWizard(models.TransientModel):
    """
    Review and optionally correct OCR-extracted invoice fields before
    they are committed to the journal entry.
    """

    _name = "fusion.extraction.review.wizard"
    _description = "Review Extracted Invoice Fields"

    # ------------------------------------------------------------------
    # Relationship
    # ------------------------------------------------------------------
    move_id = fields.Many2one(
        comodel_name="account.move",
        string="Invoice / Bill",
        required=True,
        ondelete="cascade",
        help="The journal entry whose extracted data is being reviewed.",
    )

    # ------------------------------------------------------------------
    # Extracted header fields (editable)
    # ------------------------------------------------------------------
    vendor_name = fields.Char(
        string="Vendor / Supplier Name",
        help="Name of the vendor as read by the OCR engine.",
    )
    invoice_number = fields.Char(
        string="Invoice Number",
        help="The supplier's invoice reference.",
    )
    invoice_date = fields.Date(
        string="Invoice Date",
    )
    due_date = fields.Date(
        string="Due Date",
    )
    total_amount = fields.Float(
        string="Total Amount",
        digits=(16, 2),
    )
    tax_amount = fields.Float(
        string="Tax Amount",
        digits=(16, 2),
    )
    subtotal = fields.Float(
        string="Subtotal",
        digits=(16, 2),
    )
    currency_code = fields.Char(
        string="Currency",
        help="ISO 4217 currency code (e.g. USD, EUR, CAD).",
    )

    # ------------------------------------------------------------------
    # Read-only context
    # ------------------------------------------------------------------
    raw_text = fields.Text(
        string="Raw OCR Text",
        readonly=True,
        help="Full OCR output – shown for reference while reviewing fields.",
    )
    confidence = fields.Float(
        string="Confidence (%)",
        digits=(5, 2),
        readonly=True,
    )

    # ------------------------------------------------------------------
    # Line items (JSON text field – editable as raw JSON for power users)
    # ------------------------------------------------------------------
    line_items_json = fields.Text(
        string="Line Items (JSON)",
        help=(
            "Extracted line items in JSON format.  Each item should "
            "have: description, quantity, unit_price, amount."
        ),
    )

    # ------------------------------------------------------------------
    # Correction tracking
    # ------------------------------------------------------------------
    corrections_json = fields.Text(
        string="Corrections Log (JSON)",
        readonly=True,
        help="Records which fields the user changed during review.",
    )

    # ------------------------------------------------------------------
    # Computed: original attachment preview
    # ------------------------------------------------------------------
    attachment_preview = fields.Binary(
        string="Original Scan",
        compute="_compute_attachment_preview",
    )
    attachment_filename = fields.Char(
        string="Attachment Filename",
        compute="_compute_attachment_preview",
    )

    @api.depends("move_id")
    def _compute_attachment_preview(self):
        """Fetch the first image / PDF attachment for inline preview."""
        for wiz in self:
            att = wiz.move_id._find_extractable_attachment() if wiz.move_id else None
            if att:
                wiz.attachment_preview = att.datas
                wiz.attachment_filename = att.name
            else:
                wiz.attachment_preview = False
                wiz.attachment_filename = False

    # ------------------------------------------------------------------
    # Actions
    # ------------------------------------------------------------------
    def action_apply(self):
        """Validate the (possibly corrected) fields and apply them to
        the invoice.

        Returns:
            dict: A window action returning to the updated invoice form.
        """
        self.ensure_one()

        # Build the fields dict from the wizard form
        fields_dict = {
            "vendor_name": self.vendor_name or "",
            "invoice_number": self.invoice_number or "",
            "invoice_date": str(self.invoice_date) if self.invoice_date else "",
            "due_date": str(self.due_date) if self.due_date else "",
            "total_amount": self.total_amount,
            "tax_amount": self.tax_amount,
            "subtotal": self.subtotal,
            "currency": self.currency_code or "",
        }

        # Parse line items
        line_items = []
        if self.line_items_json:
            try:
                line_items = json.loads(self.line_items_json)
                if not isinstance(line_items, list):
                    raise ValueError("Expected a JSON array.")
            except (json.JSONDecodeError, ValueError) as exc:
                raise UserError(
                    _("The line items JSON is invalid: %s", str(exc))
                ) from exc
        fields_dict["line_items"] = line_items

        # Track corrections
        self._track_corrections(fields_dict)

        # Apply to the invoice
        self.move_id._apply_extracted_fields(fields_dict)

        # Update the stored JSON on the move for audit purposes
        self.move_id.fusion_extracted_fields_json = json.dumps(
            fields_dict, default=str, indent=2,
        )

        return {
            "type": "ir.actions.act_window",
            "name": _("Invoice"),
            "res_model": "account.move",
            "res_id": self.move_id.id,
            "view_mode": "form",
            "target": "current",
        }

    def action_discard(self):
        """Close the wizard without applying any changes.

        Returns:
            dict: An action that closes the wizard window.
        """
        return {"type": "ir.actions.act_window_close"}

    def action_re_extract(self):
        """Re-run the OCR extraction pipeline from scratch.

        Useful when the user has attached a better-quality scan.

        Returns:
            dict: The action returned by
            :meth:`~FusionInvoiceExtractor.action_extract_from_attachment`.
        """
        self.ensure_one()
        return self.move_id.action_extract_from_attachment()

    # ------------------------------------------------------------------
    # Correction tracking
    # ------------------------------------------------------------------
    def _track_corrections(self, final_fields):
        """Compare the wizard values against the original extraction
        and record any user edits.

        The result is stored in :attr:`corrections_json` on the wizard
        and logged for future reference.

        Args:
            final_fields (dict): The fields the user is about to apply.
        """
        self.ensure_one()
        original = {}
        if self.move_id.fusion_extracted_fields_json:
            try:
                original = json.loads(self.move_id.fusion_extracted_fields_json)
            except (json.JSONDecodeError, TypeError):
                original = {}

        corrections = {}
        compare_keys = [
            "vendor_name", "invoice_number", "invoice_date",
            "due_date", "total_amount", "tax_amount", "subtotal", "currency",
        ]
        for key in compare_keys:
            orig_val = str(original.get(key, "") or "")
            new_val = str(final_fields.get(key, "") or "")
            if orig_val != new_val:
                corrections[key] = {
                    "original": orig_val,
                    "corrected": new_val,
                }

        if corrections:
            self.corrections_json = json.dumps(corrections, default=str, indent=2)
            _log.info(
                "Fusion OCR: user corrected %d field(s) on move %s: %s",
                len(corrections), self.move_id.id, list(corrections.keys()),
            )

            # Store corrections on the move as a note for audit trail
            body_lines = [_("<b>OCR Extraction – Manual Corrections</b><ul>")]
            for field_name, change in corrections.items():
                body_lines.append(
                    _("<li><b>%s</b>: %s → %s</li>",
                      field_name, change["original"], change["corrected"])
                )
            body_lines.append("</ul>")
            self.move_id.message_post(
                body="".join(str(l) for l in body_lines),
                message_type="comment",
                subtype_xmlid="mail.mt_note",
            )
