"""
Fusion Accounting - Intrastat Reporting

Implements the EU Intrastat statistical declaration for intra-Community
trade in goods. The module introduces:

* ``fusion.intrastat.code`` – the Combined Nomenclature (CN8) commodity
  code table that products are classified against.
* ``fusion.intrastat.report`` – a transient wizard that aggregates
  invoice data for a given period and produces declaration-ready output
  grouped by commodity code, partner country, and transaction type.

Products gain ``intrastat_code_id``, ``origin_country_id`` and
``weight`` fields; invoice lines gain ``intrastat_transaction_code``.

Reference: Regulation (EC) No 638/2004, Commission Regulation (EC)
No 1982/2004.

Original implementation by Nexa Systems Inc.
"""

import logging

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

_log = logging.getLogger(__name__)

# Standard Intrastat transaction nature codes (1-digit, most common)
INTRASTAT_TRANSACTION_CODES = [
    ("1", "1 - Purchase / Sale"),
    ("2", "2 - Return"),
    ("3", "3 - Trade without payment"),
    ("4", "4 - Processing under contract"),
    ("5", "5 - After processing under contract"),
    ("6", "6 - Repairs"),
    ("7", "7 - Military operations"),
    ("8", "8 - Construction materials"),
    ("9", "9 - Other transactions"),
]


# ======================================================================
# Intrastat Commodity Code
# ======================================================================

class FusionIntrastatCode(models.Model):
    """
    Combined Nomenclature (CN8) commodity code for Intrastat reporting.

    Each record represents a single 8-digit code from the European
    Commission's Combined Nomenclature. Products reference these codes
    to determine which statistical heading they fall under when goods
    cross EU internal borders.
    """

    _name = "fusion.intrastat.code"
    _description = "Intrastat Commodity Code"
    _order = "code"
    _rec_name = "display_name"

    code = fields.Char(
        string="CN8 Code",
        required=True,
        size=8,
        help="8-digit Combined Nomenclature code (e.g. 84713000).",
    )
    name = fields.Char(
        string="Description",
        required=True,
        translate=True,
        help="Human-readable description of the commodity group.",
    )
    active = fields.Boolean(
        string="Active",
        default=True,
    )
    supplementary_unit = fields.Char(
        string="Supplementary Unit",
        help=(
            "Unit of measurement required for this heading in addition "
            "to net mass (e.g. 'p/st' for pieces, 'l' for litres)."
        ),
    )

    _sql_constraints = [
        (
            "unique_code",
            "UNIQUE(code)",
            "The Intrastat commodity code must be unique.",
        ),
    ]

    @api.depends("code", "name")
    def _compute_display_name(self):
        for record in self:
            record.display_name = f"[{record.code}] {record.name}" if record.code else record.name or ""


# ======================================================================
# Mixin for product fields
# ======================================================================

class FusionProductIntrastat(models.Model):
    """
    Extends ``product.template`` with Intrastat-specific fields.

    These fields are used by the Intrastat report wizard to determine
    the commodity code, country of origin, and net weight of goods.
    """

    _inherit = "product.template"

    intrastat_code_id = fields.Many2one(
        comodel_name="fusion.intrastat.code",
        string="Intrastat Code",
        help="Combined Nomenclature (CN8) commodity code for this product.",
    )
    origin_country_id = fields.Many2one(
        comodel_name="res.country",
        string="Country of Origin",
        help="Country where the goods were produced or manufactured.",
    )
    intrastat_weight = fields.Float(
        string="Net Weight (kg)",
        digits="Stock Weight",
        help="Net weight in kilograms for Intrastat reporting.",
    )


# ======================================================================
# Mixin for invoice-line fields
# ======================================================================

class FusionMoveLineIntrastat(models.Model):
    """
    Extends ``account.move.line`` with an Intrastat transaction code.

    The transaction code indicates the nature of the transaction
    (purchase, return, processing, etc.) and is required in each
    Intrastat declaration line.
    """

    _inherit = "account.move.line"

    intrastat_transaction_code = fields.Selection(
        selection=INTRASTAT_TRANSACTION_CODES,
        string="Intrastat Transaction",
        help="Nature of the transaction for Intrastat reporting.",
    )


# ======================================================================
# Intrastat Report Wizard
# ======================================================================

class FusionIntrastatReport(models.TransientModel):
    """
    Wizard that aggregates invoice data into Intrastat declaration lines.

    The user selects a period and flow direction (arrivals or
    dispatches), then the wizard queries posted invoices that involve
    intra-EU partners and products with an Intrastat commodity code.

    Results are grouped by commodity code, partner country, and
    transaction nature code to produce aggregated statistical values
    (value, net mass, supplementary quantity).
    """

    _name = "fusion.intrastat.report"
    _description = "Intrastat Report Wizard"

    # ------------------------------------------------------------------
    # Fields
    # ------------------------------------------------------------------
    date_from = fields.Date(
        string="From",
        required=True,
        help="Start of the reporting period (inclusive).",
    )
    date_to = fields.Date(
        string="To",
        required=True,
        help="End of the reporting period (inclusive).",
    )
    company_id = fields.Many2one(
        comodel_name="res.company",
        string="Company",
        required=True,
        default=lambda self: self.env.company,
    )
    flow_type = fields.Selection(
        selection=[
            ("arrival", "Arrivals (Purchases)"),
            ("dispatch", "Dispatches (Sales)"),
        ],
        string="Flow",
        required=True,
        default="dispatch",
        help="Direction of goods movement across EU borders.",
    )
    line_ids = fields.One2many(
        comodel_name="fusion.intrastat.report.line",
        inverse_name="report_id",
        string="Declaration Lines",
        readonly=True,
    )
    state = fields.Selection(
        selection=[
            ("draft", "Draft"),
            ("done", "Computed"),
        ],
        string="Status",
        default="draft",
        readonly=True,
    )

    # ------------------------------------------------------------------
    # Validation
    # ------------------------------------------------------------------
    @api.constrains("date_from", "date_to")
    def _check_date_range(self):
        for record in self:
            if record.date_from and record.date_to and record.date_to < record.date_from:
                raise ValidationError(
                    _("The end date must not precede the start date.")
                )

    # ------------------------------------------------------------------
    # Computation
    # ------------------------------------------------------------------
    def action_compute(self):
        """Aggregate invoice lines into Intrastat declaration rows.

        Grouping key: ``(commodity_code, partner_country, transaction_code)``

        For each group the wizard sums:
        * ``total_value`` – invoice line amounts in company currency
        * ``total_weight`` – product net weight × quantity
        * ``supplementary_qty`` – quantity in supplementary units (if
          the commodity code requires it)
        """
        self.ensure_one()

        # Clear previous results
        self.line_ids.unlink()

        invoice_lines = self._get_eligible_lines()
        if not invoice_lines:
            raise UserError(
                _("No eligible invoice lines found for the selected period and flow type.")
            )

        aggregation = {}  # (code_id, country_id, txn_code) -> dict

        for line in invoice_lines:
            product = line.product_id.product_tmpl_id
            code = product.intrastat_code_id
            if not code:
                continue

            partner = line.partner_id
            country = partner.country_id
            if not country:
                continue

            txn_code = line.intrastat_transaction_code or "1"

            key = (code.id, country.id, txn_code)
            bucket = aggregation.setdefault(key, {
                "intrastat_code_id": code.id,
                "country_id": country.id,
                "transaction_code": txn_code,
                "total_value": 0.0,
                "total_weight": 0.0,
                "supplementary_qty": 0.0,
            })

            qty = abs(line.quantity)
            unit_weight = product.intrastat_weight or 0.0

            # Use price_subtotal as the statistical value
            bucket["total_value"] += abs(line.price_subtotal)
            bucket["total_weight"] += unit_weight * qty
            bucket["supplementary_qty"] += qty

        # Create declaration lines
        ReportLine = self.env["fusion.intrastat.report.line"]
        for vals in aggregation.values():
            vals["report_id"] = self.id
            ReportLine.create(vals)

        self.write({"state": "done"})
        return self._reopen_wizard()

    def _get_eligible_lines(self):
        """Return ``account.move.line`` records for invoices that
        qualify for Intrastat reporting.

        Eligibility criteria:
        * Invoice is posted
        * Invoice date falls within the period
        * Partner country is in the EU and differs from the company country
        * Product has an Intrastat commodity code assigned
        * Move type matches the selected flow direction
        """
        company_country = self.company_id.country_id

        if self.flow_type == "dispatch":
            move_types = ("out_invoice", "out_refund")
        else:
            move_types = ("in_invoice", "in_refund")

        lines = self.env["account.move.line"].search([
            ("company_id", "=", self.company_id.id),
            ("parent_state", "=", "posted"),
            ("date", ">=", self.date_from),
            ("date", "<=", self.date_to),
            ("move_id.move_type", "in", move_types),
            ("product_id", "!=", False),
            ("product_id.product_tmpl_id.intrastat_code_id", "!=", False),
            ("partner_id.country_id", "!=", False),
            ("partner_id.country_id", "!=", company_country.id),
        ])

        # Filter to EU countries only
        eu_countries = self._get_eu_country_ids()
        if eu_countries:
            lines = lines.filtered(
                lambda l: l.partner_id.country_id.id in eu_countries
            )

        return lines

    def _get_eu_country_ids(self):
        """Return a set of ``res.country`` IDs for current EU member states."""
        eu_group = self.env.ref("base.europe", raise_if_not_found=False)
        if eu_group:
            return set(eu_group.country_ids.ids)
        return set()

    def _reopen_wizard(self):
        """Return an action that re-opens this wizard form."""
        return {
            "type": "ir.actions.act_window",
            "res_model": self._name,
            "res_id": self.id,
            "view_mode": "form",
            "target": "new",
        }


# ======================================================================
# Intrastat Report Line
# ======================================================================

class FusionIntrastatReportLine(models.TransientModel):
    """
    One aggregated row in an Intrastat declaration.

    Each line represents a unique combination of commodity code, partner
    country, and transaction nature code. Statistical values (amount,
    weight, supplementary quantity) are totals across all qualifying
    invoice lines that match the grouping key.
    """

    _name = "fusion.intrastat.report.line"
    _description = "Intrastat Report Line"
    _order = "intrastat_code_id, country_id"

    report_id = fields.Many2one(
        comodel_name="fusion.intrastat.report",
        string="Report",
        required=True,
        ondelete="cascade",
    )
    intrastat_code_id = fields.Many2one(
        comodel_name="fusion.intrastat.code",
        string="Commodity Code",
        required=True,
        readonly=True,
    )
    country_id = fields.Many2one(
        comodel_name="res.country",
        string="Partner Country",
        required=True,
        readonly=True,
    )
    transaction_code = fields.Selection(
        selection=INTRASTAT_TRANSACTION_CODES,
        string="Transaction Type",
        readonly=True,
    )
    total_value = fields.Float(
        string="Statistical Value",
        digits="Account",
        readonly=True,
        help="Total invoice value in company currency.",
    )
    total_weight = fields.Float(
        string="Net Mass (kg)",
        digits="Stock Weight",
        readonly=True,
        help="Total net weight in kilograms.",
    )
    supplementary_qty = fields.Float(
        string="Supplementary Qty",
        digits="Product Unit of Measure",
        readonly=True,
        help="Total quantity in supplementary units (if applicable).",
    )
