from api.v1.modules.a76.app_settings.service import AppSettingsService
from decimal import Decimal
from typing import List

from sqlalchemy.orm import Session

from api.v1.modules.a24.discharges.models import DischargeDetail, DischargeHeader, DischargeStatus
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 .sub_process.review_rule_octave import borra_saldos_regla_octava
from ..balance.void_balance_entries import void_balance_entries


# ─────────────────────────────────────────────────────────────────────────────
# Validación de cantidades retornadas con detalle de exportaciones activas
# ─────────────────────────────────────────────────────────────────────────────

def _validate_returned_quantities(
    db: Session,
    invoice: InvoiceHeader,
    lines: List[LineItem],
    errors: ErrorCollector, cancelled_by: str = "SYSTEM",
) -> None:
    """
    Verifica que ninguna partida tenga saldos pendientes por exportaciones
    activas que la afecten.
    Paridad: bloque 'REVISA CANTIDADES RETORNADAS' (Clarion SCAII).

    El Clarion recorre las partidas con (CantRetornadaTemp + CantRetornada +
    CantExistencia) <> 0 y luego busca en QEqeMaq (exportaciones definitivas),
    QEqeMaqRep (exportaciones de reparación) y QEqiMaqRep (importaciones de
    reparación) para identificar qué factura de exportación activa (Estatus='AC')
    tiene esa partida descargada.

    En Python los mismos vínculos viven en:
      DischargeDetail.import_item_line_id  → la partida de importación consumida
      DischargeDetail.header               → DischargeHeader
      DischargeHeader.source_invoice_id    → InvoiceHeader (la factura de exportación)
      InvoiceHeader.status                 → InvoiceStatus.PROCESSED  (≡ Estatus='AC')

    Para cada partida con saldo se buscan DischargeDetail con status APPLIED en
    una factura de exportación procesada y se reporta qué factura debe
    desactualizarse primero.
    """
    # Importante:
    # Con la migración a tablas especializadas (discharge_* + balance_movement),
    # los campos legacy de cantidades retornadas pueden quedar desfasados.
    # Para bloquear una des-actualización solo debe considerarse descarga ACTIVA
    # real (DischargeHeader.status=APPLIED y factura fuente procesada).
    for line in lines:

        # Buscar DischargeDetail vinculados a esta partida de importación
        # cuya factura de exportación esté activa (PROCESSED).
        # Paridad: bucle sobre QEqeMaq/QEqeMaqRep donde Descarga=1 y
        #          encabezado de exportación con Estatus='AC'.
        details: List[DischargeDetail] = (
            db.query(DischargeDetail)
            .join(DischargeDetail.header)
            .filter(
                DischargeDetail.import_item_line_id == line.id,
                DischargeHeader.status == DischargeStatus.APPLIED,
            )
            .all()
        )

        active_details = [
            d for d in details
            if d.header
            and d.header.source_invoice is not None
            and d.header.source_invoice.status == InvoiceStatus.PROCESSED
        ]

        if active_details:
            # Reportar un error por cada factura de exportación activa distinta
            # (equivale a QueErr en el Clarion).
            seen_export_invoices: set = set()
            for detail in active_details:
                src_invoice = detail.header.source_invoice
                src_number  = src_invoice.invoice_number or str(src_invoice.id)

                if src_number in seen_export_invoices:
                    continue
                seen_export_invoices.add(src_number)

                export_line_number = (
                    detail.export_line.line_number if detail.export_line else "?"
                )

                errors.add_error(
                    field=f"line[{line.line_number}].discharge",
                    message=(
                        f"La Línea: {line.line_number} se ha descargado "
                        f"{detail.quantity_discharged} en la factura: {src_number} "
                        f"de exportación con Línea: {export_line_number}."
                    ),
                    solution=[
                        f"Desactualizar la factura: {src_number} "
                        "para regresar saldos a la partida."
                    ],
                    code="LINE_HAS_ACTIVE_DISCHARGE",
                )
        # Si no hay discharge activo, NO bloquear por contadores legacy.


# ─────────────────────────────────────────────────────────────────────────────
# Reset de la factura e inventario
# ─────────────────────────────────────────────────────────────────────────────

def _reset_invoice_financials(invoice: InvoiceHeader) -> None:
    """
    Reinicia los totales financieros del encabezado de la factura a cero y
    cambia el estatus a PENDING (equivalente a Estatus='NA' en Clarion).
    Paridad: UPDATE QFacImp SET CantImpo=0, PesoNeto=0, PesoBruto=0,
             Cantbultos=0, ValorImpoMN=0, ValorImpoME=0, ValorImpoMC=0,
             ValorAduanasMN=0, ValorAduanasME=0, Estatus='NA',
             ComofueProcesada='', ValorIVAMN=0, ValorIVAME=0 (Clarion SCAII).
    """
    fin = invoice.financials
    if fin is None:
        return

    fin.total_quantity   = 0.0
    fin.net_weight       = 0.0
    fin.gross_weight     = 0.0
    fin.total_packages   = 0
    fin.value_mn         = 0.0
    fin.value_me         = 0.0
    fin.value_mc         = 0.0
    fin.customs_value_mn = 0.0
    fin.customs_value_me = 0.0
    fin.iva_mn           = 0.0
    fin.iva_me           = 0.0
    fin.iva_mc           = 0.0

    invoice.status        = InvoiceStatus.PENDING
    invoice.process_method = None


def _reset_line_quantities(lines: List[LineItem]) -> None:
    """
    Reinicia los contadores de inventario de cada partida a cero.
    Paridad: UPDATE QEqiMaq SET CantRetornada=0, CantRetornadaTemp=0,
             ValorRetornadoMN=0, ValorRetornadoME=0, CantExistencia=0,
             ValorIVAMNUsado=0, ValorIVAMEUsado=0 (Clarion SCAII).
    """
    for line in lines:
        if line.financial is not None:
            line.financial.value_returned_mxn = Decimal(0)
            line.financial.value_returned_usd = Decimal(0)
            line.financial.vat_used_mxn       = Decimal(0)
            line.financial.vat_used_usd       = Decimal(0)


# ─────────────────────────────────────────────────────────────────────────────
# Proceso principal de reversión
# ─────────────────────────────────────────────────────────────────────────────

def revert_process(
    db: Session,
    invoice: InvoiceHeader,
    lines: List[LineItem],
    tenant_id: str,
    company_id: str,
    errors: ErrorCollector, cancelled_by: str = "SYSTEM",
) -> list:
    """
    Proceso principal de des-actualización de una factura de importación
    temporal.
    Paridad: rutina principal 'DESACTUALIZAR FACTURA IMPO TEMP' (Clarion SCAII).

    Flujo:
      1. Verifica que ninguna partida tenga saldos activos por exportaciones
         procesadas (QueueErrorAct en Clarion). Si los hay → ValidationException.
      2. Si no hay errores de validación:
         a. Do BORRASALDOS_REGLA_OCTAVA — revierte cupos de Regla Octava.
         b. UPDATE QFacImp — reinicia totales del encabezado (status → PENDING).
         c. UPDATE QEqiMaq — reinicia contadores de inventario por partida.
      3. Retorna sql_errors (errores no-bloqueantes de BD, equivalente a
         QueueErrorSQL en Clarion).

    Raises:
        ValidationException: si hay partidas con descargas activas
                             (equivale a Records(QueueErrorAct) <> 0).
    """
    # ── Paso 1: REVISA CANTIDADES RETORNADAS ──────────────────────────────────
    _validate_returned_quantities(db, invoice, lines, errors)
    errors.raise_if_errors()

    # ── Paso 2a: Do BORRASALDOS_REGLA_OCTAVA ─────────────────────────────────
    sql_errors: list = []
    borra_saldos_regla_octava(
        db=db,
        invoice_import=invoice.invoice_number or "",
        tenant_id=tenant_id,
        company_id=company_id,
        sql_errors=sql_errors,
    )

    # ── Paso 2b: UPDATE QFacImp ───────────────────────────────────────────────
    _reset_invoice_financials(invoice)

    # ── Paso 2c: UPDATE QEqiMaq ───────────────────────────────────────────────
    _reset_line_quantities(lines)

    # ── Paso 2d: Anular saldos en a24.balance_movement ───────────────────────
    # Inserta ENTRY_VOID por cada ENTRY abierto de esta factura, dejando el
    # balance neto en 0 para que las descargas de exportación no puedan
    # consumir esos lotes.  El guard interno confirma que no haya consumos
    # activos (ya validado arriba, pero se mantiene como doble seguro).
    void_balance_entries(db, invoice)

    
    # Auditoría de Desactualización
    settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id))
    q_gen = settings.get("qsisgen", {})
    s_gen = settings.get("ssisgen", {})
    act_seguridad = int(q_gen.get("actseguridad") or s_gen.get("actseguridad") or q_gen.get("ActSeguridad") or s_gen.get("ActSeguridad", 0))
    
    if act_seguridad == 1:
        from api.v1.modules.a76.audit_log.services.service import AuditService
        AuditService.create_audit_log(
            db=db, reference=invoice.invoice_number, procedure="ANULAR FACTURA", movement="DESACTUALIZACION",
            username=cancelled_by or "SYSTEM", tenant_id=int(tenant_id), company_id=int(company_id),
            table_name="invoice_header", record_id=str(invoice.id), operation_type="UPDATE"
        )
    
    return sql_errors
