"""
Fusion Accounting - EDI Format Registry

Provides a configuration model for registering electronic document
interchange formats.  Each format record carries a unique code and
delegates the actual XML generation / parsing to dedicated generator
classes (e.g. ``FusionUBLGenerator``, ``FusionCIIGenerator``).

Administrators may restrict a format to customer invoices, vendor bills,
or allow it for both through the ``applicable_to`` field.

Original implementation by Nexa Systems Inc.
"""

import logging

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

_log = logging.getLogger(__name__)


class FusionEDIFormat(models.Model):
    """
    Registry entry for a supported EDI format.

    This model acts as a strategy-pattern dispatcher: the ``code`` field
    selects the concrete generator/parser to invoke when creating or
    reading an electronic document.  Format records are typically seeded
    via XML data files and should not be deleted while EDI documents
    reference them.
    """

    _name = "fusion.edi.format"
    _description = "Fusion EDI Format"
    _order = "sequence, name"
    _rec_name = "name"

    # ------------------------------------------------------------------
    # Fields
    # ------------------------------------------------------------------
    name = fields.Char(
        string="Format Name",
        required=True,
        help="Human-readable name shown in selection lists.",
    )
    code = fields.Char(
        string="Code",
        required=True,
        help=(
            "Unique technical identifier used to dispatch generation / "
            "parsing logic. Examples: 'ubl_21', 'cii', 'facturx'."
        ),
    )
    description = fields.Text(
        string="Description",
        help="Optional notes about the format, its version, or usage.",
    )
    applicable_to = fields.Selection(
        selection=[
            ("invoices", "Customer Invoices / Credit Notes"),
            ("bills", "Vendor Bills"),
            ("both", "Both"),
        ],
        string="Applicable To",
        default="both",
        required=True,
        help="Restricts this format to customer-side, vendor-side, or both.",
    )
    active = fields.Boolean(
        string="Active",
        default=True,
        help="Inactive formats are hidden from selection lists.",
    )
    sequence = fields.Integer(
        string="Sequence",
        default=10,
        help="Controls display ordering in lists and dropdowns.",
    )

    # ------------------------------------------------------------------
    # Constraints
    # ------------------------------------------------------------------
    _sql_constraints = [
        (
            "code_unique",
            "UNIQUE(code)",
            "Each EDI format must have a unique code.",
        ),
    ]

    # ------------------------------------------------------------------
    # Generation / Parsing Dispatch
    # ------------------------------------------------------------------
    def generate_document(self, move):
        """Generate an electronic document for the given journal entry.

        Dispatches to the appropriate generator based on ``self.code``.

        Args:
            move: An ``account.move`` recordset (single record).

        Returns:
            bytes: The XML payload of the generated document.

        Raises:
            UserError: When no generator is registered for this format
                code or the move type is incompatible.
        """
        self.ensure_one()
        move.ensure_one()
        self._check_applicability(move)

        generator_map = self._get_generator_map()
        generator_method = generator_map.get(self.code)
        if not generator_method:
            raise UserError(
                _("No generator is registered for EDI format '%s'.", self.code)
            )
        return generator_method(move)

    def parse_document(self, xml_bytes):
        """Parse an incoming EDI XML document and return invoice data.

        Dispatches to the appropriate parser based on ``self.code``.

        Args:
            xml_bytes (bytes): Raw XML content to parse.

        Returns:
            dict: A dictionary of invoice field values ready for
            ``account.move.create()``.

        Raises:
            UserError: When no parser is registered for this format code.
        """
        self.ensure_one()
        parser_map = self._get_parser_map()
        parser_method = parser_map.get(self.code)
        if not parser_method:
            raise UserError(
                _("No parser is registered for EDI format '%s'.", self.code)
            )
        return parser_method(xml_bytes)

    # ------------------------------------------------------------------
    # Internal dispatch helpers
    # ------------------------------------------------------------------
    def _get_generator_map(self):
        """Return a mapping of format codes to generator callables.

        Each callable accepts a single ``account.move`` record and
        returns ``bytes`` (the XML payload).
        """
        ubl = self.env["fusion.ubl.generator"]
        cii = self.env["fusion.cii.generator"]
        return {
            "ubl_21": ubl.generate_ubl_invoice,
            "cii": cii.generate_cii_invoice,
            "facturx": cii.generate_cii_invoice,
        }

    def _get_parser_map(self):
        """Return a mapping of format codes to parser callables.

        Each callable accepts ``bytes`` (raw XML) and returns a ``dict``
        of invoice values.
        """
        ubl = self.env["fusion.ubl.generator"]
        cii = self.env["fusion.cii.generator"]
        return {
            "ubl_21": ubl.parse_ubl_invoice,
            "cii": cii.parse_cii_invoice,
            "facturx": cii.parse_cii_invoice,
        }

    def _check_applicability(self, move):
        """Verify that this format is applicable to the given move type.

        Raises:
            UserError: When the format/move combination is invalid.
        """
        self.ensure_one()
        if self.applicable_to == "invoices" and move.move_type in (
            "in_invoice",
            "in_refund",
        ):
            raise UserError(
                _(
                    "EDI format '%s' is restricted to customer invoices / "
                    "credit notes and cannot be used for vendor bills.",
                    self.name,
                )
            )
        if self.applicable_to == "bills" and move.move_type in (
            "out_invoice",
            "out_refund",
        ):
            raise UserError(
                _(
                    "EDI format '%s' is restricted to vendor bills and "
                    "cannot be used for customer invoices.",
                    self.name,
                )
            )
