"""
register_discharge_ledger
=========================
Creates the full Annex-24 discharge record for one export invoice:

  1. ONE  DischargeHeader  (one per export event)
  2. N    BalanceMovement   rows (type=CONSUMPTION, one per lot consumed)
  3. N    DischargeDetail   rows (one per export-line x import-lot pair),
         each referencing its BalanceMovement (design rule 3)

Design rules from a24.balance_movement (preserved here):
  1. NEVER update existing balance_movement rows — only INSERT.
  2. Balance = SUM of movements.
  3. Every DischargeDetail.movement_id MUST reference a BalanceMovement row.
  4. order_peps = movement.id  (set after flush, globally monotonic).
"""

import datetime
import logging
from decimal import Decimal
from typing import List, Optional

from sqlalchemy.orm import Session

from api.v1.modules.a76.invoices.models import InvoiceHeader
from api.v1.modules.a24.balance_movements.models import BalanceMovement, MovementType
from api.v1.modules.a24.discharges.models import (
    DischargeDetail,
    DischargeHeader,
    DischargeStatus,
    DischargeType,
)
from api.v1.modules.a76.invoices.models import InvoiceHeader as A76InvoiceHeader
from api.v1.modules.a76.items.models import LineItem
from .discharge_types import DownloadEntry, AvailableLot

logger = logging.getLogger(__name__)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _discharge_type_for_invoice(invoice: InvoiceHeader) -> DischargeType:
    mapping = {
        "AFIJO": DischargeType.TEMPORARY,
        "DONAC": DischargeType.TEMPORARY,
        "SCRAP": DischargeType.WASTE_SCRAP,
        "REEXP": DischargeType.DEFINITIVE,
        "VEMEX": DischargeType.DEFINITIVE,
    }
    return mapping.get(invoice.invoice_type or "", DischargeType.TEMPORARY)


def _export_date(invoice: InvoiceHeader) -> datetime.date:
    d = invoice.invoice_date
    return d.date() if hasattr(d, "date") else d


def _proportional_value(
    consume: Decimal,
    lot_consumed_total: Decimal,
    lot_value: Optional[Decimal],
) -> Optional[Decimal]:
    """Returns the proportional value for *consume* units out of *lot_consumed_total*."""
    if not lot_value or lot_consumed_total <= 0:
        return None
    return (consume / lot_consumed_total) * lot_value


def _proportional_qty(consume: Decimal, base_qty: Optional[Decimal], base_total: Optional[Decimal]) -> Optional[Decimal]:
    """
    Proratea un valor (peso/valor) en proporción a lo consumido.
    - consume: cantidad consumida del lote
    - base_qty: valor total del lote (ej. peso neto total del lote)
    - base_total: cantidad total del lote (ej. quantity del lote)
    """
    if base_qty is None:
        return None
    if base_total is None or base_total <= 0:
        return None
    return (consume / base_total) * base_qty


# ---------------------------------------------------------------------------
# Public entry point
# ---------------------------------------------------------------------------

def register_discharge_ledger(
    db: Session,
    export_invoice: InvoiceHeader,
    to_discharge: List[DownloadEntry],
) -> Optional[DischargeHeader]:
    """
    Persists the complete Annex-24 discharge record for *export_invoice*.

    Expects that compare_balances() has already run and populated
    ``lot.consumed_qty`` for every lot that was drawn from.

    Returns the created DischargeHeader, or None if nothing was discharged.
    """
    # Only process entries that actually consumed something
    active = [e for e in to_discharge if e.quantity_used > Decimal(0)]
    if not active:
        return None

    op_date = _export_date(export_invoice)
    discharge_type = _discharge_type_for_invoice(export_invoice)

    # Caches to avoid N+1 queries in loops
    export_line_cache: dict[int, LineItem] = {}
    import_line_cache: dict[int, LineItem] = {}
    import_invoice_number_cache: dict[int, str] = {}

    # ── 1. DischargeHeader ────────────────────────────────────────────────
    header = DischargeHeader(
        tenant_id=export_invoice.tenant_id,
        company_id=export_invoice.company_id,
        source_invoice_id=export_invoice.id,
        discharge_type=discharge_type,
        status=DischargeStatus.APPLIED,
        discharge_date=op_date,
    )
    db.add(header)
    db.flush()  # get header.id

    total_movements = 0

    for entry in active:
        export_line_id: Optional[int] = entry.line_item_id

        export_line_obj: Optional[LineItem] = None
        if export_line_id:
            export_line_obj = export_line_cache.get(export_line_id)
            if export_line_obj is None:
                export_line_obj = db.get(LineItem, export_line_id)
                if export_line_obj is not None:
                    export_line_cache[export_line_id] = export_line_obj

        # Only iterate lots that were actually consumed
        consumed_lots: List[AvailableLot] = [
            lot for lot in entry.available_lots if lot.consumed_qty > Decimal(0)
        ]

        for lot in consumed_lots:
            consume = lot.consumed_qty

            # Load import-line object for denormalized customs/weights fields
            import_line_obj = import_line_cache.get(lot.import_item_line_id)
            if import_line_obj is None:
                import_line_obj = db.get(LineItem, lot.import_item_line_id)
                if import_line_obj is not None:
                    import_line_cache[lot.import_item_line_id] = import_line_obj

            # Import invoice number (for origin_import_invoice in DischargeDetail)
            origin_import_invoice: Optional[str] = None
            if lot.import_invoice_id:
                origin_import_invoice = import_invoice_number_cache.get(lot.import_invoice_id)
                if origin_import_invoice is None:
                    inv = db.get(A76InvoiceHeader, lot.import_invoice_id)
                    origin_import_invoice = inv.invoice_number if inv else None
                    if origin_import_invoice:
                        import_invoice_number_cache[lot.import_invoice_id] = origin_import_invoice

            # ── 2. BalanceMovement (CONSUMPTION) ──────────────────────────
            # Proportional value: consume / lot_consumed_total x lot_value
            # lot_consumed_total == consume for single-lot entries (most cases)
            value_me = _proportional_value(consume, consume, lot.value_me)
            value_mn = _proportional_value(consume, consume, lot.value_mn)
            imp_qty = import_line_obj.quantity if import_line_obj else None
            imp_total_qty = imp_qty.quantity if imp_qty else None
            net_weight = _proportional_qty(
                consume,
                imp_qty.net_weight if imp_qty else None,
                imp_total_qty,
            )

            movement = BalanceMovement(
                tenant_id=export_invoice.tenant_id,
                company_id=export_invoice.company_id,
                import_invoice_id=lot.import_invoice_id,
                import_item_line_id=lot.import_item_line_id,
                part_number_id=lot.part_number_id,
                movement_type=MovementType.CONSUMPTION,
                quantity=consume,
                value_me=value_me,
                value_mn=value_mn,
                net_weight=net_weight,
                source_invoice_id=export_invoice.id,
                source_item_line_id=export_line_id,
                order_peps=0,   # placeholder — set after flush (rule 4)
                operation_date=op_date,
                notes=(
                    f"Descarga por factura de exportación "
                    f"{export_invoice.invoice_number}"
                ),
            )
            db.add(movement)
            db.flush()  # get movement.id
            movement.order_peps = movement.id  # rule 4: monotonic

            # ── 3. DischargeDetail ─────────────────────────────────────────
            # Denormalized fields expected by reports:
            imp_cust = import_line_obj.customs if import_line_obj else None
            # Reuse already computed net_weight for consistency with movement.
            gross_weight = _proportional_qty(consume, imp_qty.gross_weight if imp_qty else None, imp_total_qty)

            detail = DischargeDetail(
                tenant_id=export_invoice.tenant_id,
                company_id=export_invoice.company_id,
                discharge_header_id=header.id,
                export_item_line_id=export_line_id,
                import_item_line_id=lot.import_item_line_id,
                movement_id=movement.id,
                quantity_discharged=consume,
                unit_of_measure=entry.unit_of_measure or None,
                value_me=value_me,
                value_mn=value_mn,
                net_weight=net_weight,
                gross_weight=gross_weight,
                tariff_fraction=imp_cust.fraction if imp_cust else None,
                fraction_type=imp_cust.fraction_type if imp_cust else None,
                ad_valorem=imp_cust.advalorem if imp_cust else None,
                country_of_origin=imp_cust.origin_country if imp_cust else None,
                sector=imp_cust.sector if imp_cust else None,
                procedence=entry.origin_procedure or None,
                part_number=entry.part_number or None,
                export_part_number=(
                    export_line_obj.part_info.part_number
                    if export_line_obj and export_line_obj.part_info and export_line_obj.part_info.part_number
                    else None
                ),
                origin_import_invoice=origin_import_invoice,
            )
            db.add(detail)
            total_movements += 1

    logger.info(
        "register_discharge_ledger: invoice=%s header_id=%s details=%d",
        export_invoice.invoice_number,
        header.id,
        total_movements,
    )
    return header
