"""
finalize_invoice_no_discharge / finalize_invoice_with_discharge
(TERMINA_AC_O_LP_NODES / TERMINA_AC_O_LP_NORMAL)

Last step of export invoice processing.  Both variants:
  1. Review enabled classes (REVISACLASESHABILITADAS).
  2. Validate SisExp quantity / weight / value limits (min and max).
  3. If no errors: assign invoice-level totals and mark as PROCESSED.

The "with_discharge" variant additionally:
  4. Generate definitive import (GENERAIMPODEFINITIVA) (if is_regime_change and generate_id)
  5. Register discharge in import lines (REGISTRA_DESCARGA_IMPORTACION)
  6. Register series discharge (REGISTRA_DESCARGA_SERIES)

The legacy 'Of LP' branch (print-preview / progress-bar UI) is not ported.
"""

import datetime
from decimal import Decimal
from typing import TYPE_CHECKING, List

from sqlalchemy.orm import Session

from .register_import_discharge import register_import_discharge
from .register_discharge_series import register_discharge_series
from .register_discharge_ledger import register_discharge_ledger

if TYPE_CHECKING:
    from .discharge_types import DownloadEntry

from api.v1.modules.a76.classes.models import Class
from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceStatus
from api.v1.modules.a76.items.models import LineItem
from core.exceptions import ErrorCollector
from .review_limits import limit_weight, limit_value
from .generate_definitive_import import generate_definitive_import, is_regime_change_detected


# ---------------------------------------------------------------------------
# REVISACLASESHABILITADAS
# ---------------------------------------------------------------------------

def _review_enabled_classes(
    db: Session,
    invoice: InvoiceHeader,
    lines: List[LineItem],
    errors: ErrorCollector,
) -> None:
    """
    REVISACLASESHABILITADAS
    Verifies that every line item's class is active (not disabled).

    Clarion: loops QEqeMaq for the invoice, fetches QClaAct by class code,
    errors if HabilitaDeshabilitaClase = 1  →  Python: Class.is_active = False.
    """
    for line in lines:
        if not line.class_id:
            continue
        cls: Class | None = db.get(Class, line.class_id)
        if cls is not None and cls.is_active is False:
            errors.add_error(
                field=f"line[{line.line_number}].class",
                message=(
                    f"La Clase: '{cls.class_code}' esta desactivada, "
                    "no se pueden hacer movimientos."
                ),
                solution=["Seleccionar una clase activa."],
                code="CLASS_DISABLED",
            )


# ---------------------------------------------------------------------------
# DO ASIGNA_VALORES_FACTURA
# ---------------------------------------------------------------------------
from api.v1.modules.a76.app_settings.service import AppSettingsService
from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate
from sqlalchemy import select
from .review_limits import review_sisexpo_limits

def _assign_invoice_totals(
    db: Session,
    invoice: InvoiceHeader,
    lines: List[LineItem],
    tenant_id: str,
    company_id: str,
    username: str = "SYSTEM"
) -> None:
    """
    DO ASIGNA_VALORES_FACTURA
    Calcula totales, resuelve TC base pedimento si aplica, y marca como PROCESADO.
    """
    settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id))
    
    inv_type = (invoice.invoice_type or "").strip().upper()
    op_type = (invoice.operation_type or "").strip().lower() # 'imp' or 'exp'
    
    # Hierarchical resolve: invoices.types.{op}.{type}.ssisgen.ssimpFormData
    inv_map = settings.get("invoices", {}).get("types", {}).get(op_type, {}).get(inv_type, {})
    form_data = inv_map.get("ssisgen", {}).get("ssimpFormData", {}) or inv_map.get("qsisgen", {}).get("ssimpFormData", {}) or settings.get("ssisgen", {}) or settings.get("qsisgen", {})

    cal_val_base_tc = int(form_data.get("calvalbasetcpedexpo") or form_data.get("CalValBaseTCPedExpo") or 0)
    act_seguridad = int(form_data.get("actseguridad") or form_data.get("ActSeguridad") or 0)

    total_value_mn  = Decimal(0)
    total_value_me  = Decimal(0)
    total_qty       = Decimal(0)
    total_packages  = 0
    total_net_weight   = Decimal(0)
    total_gross_weight = Decimal(0)

    for line in lines:
        if line.financial:
            total_value_mn += Decimal(str(line.financial.value_mxn or 0))
            total_value_me += Decimal(str(line.financial.value_usd or 0))
        if line.quantity:
            total_qty          += line.quantity.quantity        or Decimal(0)
            total_packages     += line.quantity.package_quantity or 0
            total_net_weight   += line.quantity.net_weight      or Decimal(0)
            total_gross_weight += line.quantity.gross_weight    or Decimal(0)

    # 1. Resolver TC base pedimento si aplica
    if cal_val_base_tc == 1:
        if invoice.compliance_mx and invoice.compliance_mx.pedimento:
            ped = invoice.compliance_mx.pedimento
            if ped.pedimento_dates and ped.pedimento_dates.payment_date:
                payment_date = ped.pedimento_dates.payment_date
                stmt = select(ExchangeRate).where(
                    ExchangeRate.tenant_id == int(tenant_id),
                    ExchangeRate.company_id == int(company_id),
                    ExchangeRate.date == payment_date.date())
                ex_rate_row = db.execute(stmt).scalar_one_or_none()
                if ex_rate_row and invoice.financials:
                    invoice.financials.exchange_rate = float(ex_rate_row.value)
                    invoice.compliance_mx.which_exchange_rate = "TCPED"
                    invoice.process_log = "Se Actualizó con el Tipo de Cambio de la Fecha de Pago de Pedimento."
    
    # 2. Asignar totales al header
    if invoice.financials is not None:
        invoice.financials.value_mn      = float(total_value_mn)
        invoice.financials.value_me      = float(total_value_me)
        invoice.financials.total_quantity  = float(total_qty)
        invoice.financials.total_packages  = total_packages
        invoice.financials.net_weight      = float(total_net_weight)
        invoice.financials.gross_weight    = float(total_gross_weight)

    invoice.party_count  = len([l for l in lines if not (l.fa_data and l.fa_data.is_subitem)])
    invoice.updated_date = datetime.date.today()
    invoice.status       = InvoiceStatus.PROCESSED

    # 3. Auditoría (ActSeguridad)
    if act_seguridad == 1:
        invoice.updated_by = username
        from api.v1.modules.a76.audit_log.services.service import AuditService
        AuditService.create_audit_log(
            db=db, reference=invoice.invoice_number, procedure="ACTUALIZAR FACTURA", movement="EXPORTACION",
            username=username, tenant_id=int(tenant_id), company_id=int(company_id),
            table_name="invoice_header", record_id=str(invoice.id), operation_type="UPDATE"
        )


# ---------------------------------------------------------------------------
# Public entry points
# ---------------------------------------------------------------------------

def finalize_invoice_no_discharge(
    db: Session,
    invoice: InvoiceHeader,
    lines: List[LineItem],
    tenant_id: str,
    company_id: str,
    errors: ErrorCollector,
    username: str = "SYSTEM",
) -> None:
    """
    TERMINA_AC_O_LP_NODES
    Finalizes a NODES-type export invoice (no inventory discharge).
    """
    _review_enabled_classes(db, invoice, lines, errors)

    total_qty, total_net_weight = limit_weight(lines)
    total_value                 = limit_value(lines)

    settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id))
    review_sisexpo_limits(invoice, settings, total_qty, total_net_weight, total_value, errors)

    if not errors.has_errors():
        _assign_invoice_totals(db, invoice, lines, tenant_id, company_id, username)


def finalize_invoice_with_discharge(
    db: Session,
    invoice: InvoiceHeader,
    lines: List[LineItem],
    tenant_id: str,
    company_id: str,
    errors: ErrorCollector,
    to_discharge: List["DownloadEntry"] | None = None,
    username: str = "SYSTEM",
) -> None:
    # Refrescar factura para asegurar info de compliance/pedimentos fresca
    db.refresh(invoice)
    _review_enabled_classes(db, invoice, lines, errors)

    total_qty, total_net_weight = limit_weight(lines)
    total_value                 = limit_value(lines)

    settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id))
    review_sisexpo_limits(invoice, settings, total_qty, total_net_weight, total_value, errors)

    if not errors.has_errors():
        # Unified detection of Regime Change (F4, CR type, or Manual Flag)
        if is_regime_change_detected(invoice, settings):
            def_invoice, created = generate_definitive_import(db, invoice, to_discharge or [], settings, errors)
            if created and not errors.has_errors():
                from .generate_definitive_import import (
                    generate_definitive_import_all_lines,
                    generate_definitive_import_discharged_lines
                )
                # Choice based on generate_desc_parties
                if (invoice.generate_desc_parties or "").strip() == 'Todas':
                    generate_definitive_import_all_lines(db, invoice, def_invoice, errors)
                else:
                    generate_definitive_import_discharged_lines(
                        db, invoice, def_invoice, to_discharge or [], errors
                    )

        if to_discharge:
            # Write BalanceMovement (CONSUMPTION) + DischargeHeader + DischargeDetail
            register_discharge_ledger(db, invoice, to_discharge)
            # Update returned values on the import lines
            register_import_discharge(db, invoice, to_discharge)
            register_discharge_series(db, invoice, to_discharge)

        _assign_invoice_totals(db, invoice, lines, tenant_id, company_id, username)
