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

from sqlalchemy import select
from sqlalchemy.orm import Session

from api.v1.modules.a24.discharges.models import (
    DischargeDetail,
    DischargeHeader,
    DischargeStatus,
)
from api.v1.modules.a24.balance_movements.models import BalanceMovement, MovementType
from api.v1.modules.a76.invoices.models import (
    InvoiceHeader,
    InvoiceStatus,
    OperationType,
)
from api.v1.modules.a76.items.models import LineItem
from api.v1.modules.a76.items.series.models import Serie
from core.exceptions import ErrorCollector

_VAT_CUTOFF = datetime.date(2014, 12, 31)  # Clarion day 78165


def _validate_regime_change_definitive_invoice_exists(
    db: Session,
    invoice: InvoiceHeader,
    errors: ErrorCollector,
) -> None:
    # Robust detection logic (same as generator)
    is_cr = False
    if invoice.compliance_mx and invoice.compliance_mx.is_regime_change:
        is_cr = True
    elif (invoice.invoice_type or "").strip().upper() == "CR":
        is_cr = True
    elif invoice.operation_type == OperationType.EXP:
        if invoice.compliance_mx and invoice.compliance_mx.pedimento:
            if (invoice.compliance_mx.pedimento.pedimento_code or "").strip().upper() == "F4":
                is_cr = True
    
    if not is_cr:
        return

    if not invoice.invoice_number:
        return

    exists = (
        db.query(InvoiceHeader.id)
        .filter(
            InvoiceHeader.tenant_id == invoice.tenant_id,
            InvoiceHeader.company_id == invoice.company_id,
            InvoiceHeader.operation_type == OperationType.IMP,            
            InvoiceHeader.invoice_type == "DEF",  # Importación Definitiva generada
            InvoiceHeader.invoice_number == invoice.invoice_number,
        )
        .first()
        is not None
    )

    if exists:
        errors.add_error(
            field="invoice_number",
            message=(
                "Error: Existe una Factura de Importación Definitiva a partir "
                "de esta Factura."
            ),
            solution=[
                f"Desactualizar y borrar la Factura: {invoice.invoice_number} "
                "de Importación Definitiva."
            ],
            code="DEFINITIVE_IMPORT_ALREADY_EXISTS",
            value=invoice.invoice_number,
        )


def _check_access_lock(invoice: InvoiceHeader) -> None:
    """
    Clarion block against GAccesosModulos (security lock by terminal/user).
    Currently implemented as status-based concurrency lock in the Celery task.
    """
    _ = invoice



def _return_discharged_quantities(
    db: Session,
    export_invoice: InvoiceHeader,
    lines: List[LineItem],
) -> None:
    """
    REGRESA_CANT_RETORNADAS
    Returns discharged quantities/values to their source import invoice lines.

    Uses the new specialized discharge tables:
      a24.discharge_header + a24.discharge_detail

    We only revert details that belong to this export invoice and are applied.
    This guarantees parity with the actual discharge ledger (instead of relying
    on editable UI references in fa_data).
    """
    _ = lines  # source of truth is discharge tables

    details: List[DischargeDetail] = (
        db.query(DischargeDetail)
        .join(DischargeHeader, DischargeHeader.id == DischargeDetail.discharge_header_id)
        .filter(
            DischargeHeader.source_invoice_id == export_invoice.id,
            DischargeHeader.status == DischargeStatus.APPLIED,
            DischargeDetail.tenant_id == export_invoice.tenant_id,
            DischargeDetail.company_id == export_invoice.company_id,
        )
        .all()
    )

    for detail in details:
        qty_exported = Decimal(str(detail.quantity_discharged or 0))
        if qty_exported <= 0:
            continue

        import_line = db.get(LineItem, detail.import_item_line_id)
        if (
            import_line is None
            or import_line.quantity is None
            or import_line.financial is None
        ):
            continue

        import_invoice = db.get(InvoiceHeader, import_line.invoice_id)
        if import_invoice is None:
            continue

        qty_rec = import_line.quantity
        fin = import_line.financial
        original_qty = Decimal(str(qty_rec.quantity or 0))
        if original_qty == 0:
            continue

        value_mxn = Decimal(str(fin.value_mxn or 0))
        value_usd = Decimal(str(fin.value_usd or 0))
        returned_mxn = qty_exported * value_mxn / original_qty
        returned_usd = qty_exported * value_usd / original_qty

        # Reverse monetary returned values
        # Clarion shows '-' for TEM and '+' for DEF; in the current ledger migration,
        # register_import_discharge adds both TEM/DEF, so revert subtracts both.
        fin.value_returned_mxn = (fin.value_returned_mxn or Decimal(0)) - returned_mxn
        fin.value_returned_usd = (fin.value_returned_usd or Decimal(0)) - returned_usd

        # TEM VAT recalculation by header date cutoff
        if (detail.procedence or "").upper() == "TEM":
            inv_date = import_invoice.invoice_date
            if isinstance(inv_date, datetime.datetime):
                inv_date = inv_date.date()
            if inv_date and inv_date >= _VAT_CUTOFF:
                iva_factor = Decimal(0)
                if import_invoice.financials and import_invoice.financials.iva_factor:
                    iva_factor = Decimal(str(import_invoice.financials.iva_factor))
                fin.vat_used_mxn = (fin.value_returned_mxn or Decimal(0)) * iva_factor / 100
                fin.vat_used_usd = (fin.value_returned_usd or Decimal(0)) * iva_factor / 100
            else:
                fin.vat_used_mxn = Decimal(0)
                fin.vat_used_usd = Decimal(0)


def _unmark_returned_series(
    db: Session,
    export_invoice: InvoiceHeader,
    lines: List[LineItem],
) -> None:
    """
    DESMARCA_SERIES_RETORNADAS
    Resets import serie discharge marks that were set by this export invoice.

    New data-source logic:
    - Uses a24.discharge_header/detail to identify which import lines were
      consumed by this export invoice.
    - Uses a76.item_line_series on export lines (discharge=True) to resolve the
      import serie row (serie_row) or by serial number fallback.
    """
    line_ids = [ln.id for ln in lines if ln.id is not None]
    if not line_ids:
        return

    # All discharge details belonging to this export invoice, grouped by export line.
    details: List[DischargeDetail] = (
        db.query(DischargeDetail)
        .join(DischargeHeader, DischargeHeader.id == DischargeDetail.discharge_header_id)
        .filter(
            DischargeHeader.source_invoice_id == export_invoice.id,
            DischargeHeader.status == DischargeStatus.APPLIED,
            DischargeDetail.export_item_line_id.in_(line_ids),
            DischargeDetail.tenant_id == export_invoice.tenant_id,
            DischargeDetail.company_id == export_invoice.company_id,
        )
        .all()
    )

    import_lines_by_export: dict[int, set[int]] = {}
    for d in details:
        if d.export_item_line_id is None:
            continue
        import_lines_by_export.setdefault(d.export_item_line_id, set()).add(d.import_item_line_id)

    if not import_lines_by_export:
        return

    # Export series marked for discharge (equivalent to SerExpo.Marca = 1)
    export_series: List[Serie] = (
        db.execute(
            select(Serie).where(
                Serie.line_item_id.in_(line_ids),
                Serie.discharge == True,  # noqa: E712
            )
        )
        .scalars()
        .all()
    )

    for ex_serie in export_series:
        candidate_import_lines = import_lines_by_export.get(ex_serie.line_item_id, set())
        if not candidate_import_lines:
            continue

        import_serie: Optional[Serie] = None
        # 1) Prefer explicit mapped row from export serie
        if ex_serie.serie_row is not None:
            import_serie = db.execute(
                select(Serie).where(
                    Serie.line_item_id.in_(candidate_import_lines),
                    Serie.row == ex_serie.serie_row,
                )
            ).scalar_one_or_none()

        # 2) Fallback by serial number if row is absent
        if import_serie is None and ex_serie.serial_numbers:
            import_serie = db.execute(
                select(Serie).where(
                    Serie.line_item_id.in_(candidate_import_lines),
                    Serie.serial_numbers == ex_serie.serial_numbers,
                )
            ).scalar_one_or_none()

        if import_serie is None:
            continue

        # Clarion equivalent: SerImp:SerieExpo = 0 / SerDef:SerieExpo = 0
        import_serie.discharge = False


def _cancel_discharge_records(
    db: Session,
    export_invoice: InvoiceHeader,
    cancelled_by: Optional[str] = None,
) -> None:
    """
    Cancels discharge records created by this export invoice in the new
    specialized tables:
      - a24.discharge_header: status -> CANCELLED
      - a24.balance_movement: insert RETURN per discharge_detail row

    This is the ledger-safe equivalent of undoing "Descarga=1" effects.
    """
    headers: List[DischargeHeader] = (
        db.query(DischargeHeader)
        .filter(
            DischargeHeader.source_invoice_id == export_invoice.id,
            DischargeHeader.tenant_id == export_invoice.tenant_id,
            DischargeHeader.company_id == export_invoice.company_id,
            DischargeHeader.status.in_([DischargeStatus.APPLIED, DischargeStatus.PARTIAL]),
        )
        .all()
    )
    if not headers:
        return

    op_date = (
        export_invoice.invoice_date.date()
        if hasattr(export_invoice.invoice_date, "date")
        else export_invoice.invoice_date
    )

    for header in headers:
        details: List[DischargeDetail] = (
            db.query(DischargeDetail)
            .filter(
                DischargeDetail.discharge_header_id == header.id,
                DischargeDetail.tenant_id == export_invoice.tenant_id,
                DischargeDetail.company_id == export_invoice.company_id,
            )
            .all()
        )

        for detail in details:
            # Reverse each consumed lot with a RETURN movement (append-only ledger)
            ret_mov = BalanceMovement(
                tenant_id=export_invoice.tenant_id,
                company_id=export_invoice.company_id,
                import_invoice_id=detail.import_line.invoice_id if detail.import_line else None,
                import_item_line_id=detail.import_item_line_id,
                part_number_id=None,
                movement_type=MovementType.RETURN,
                quantity=detail.quantity_discharged or Decimal(0),
                value_me=detail.value_me,
                value_mn=detail.value_mn,
                net_weight=detail.net_weight,
                source_invoice_id=export_invoice.id,
                source_item_line_id=detail.export_item_line_id,
                order_peps=0,  # set post-flush
                operation_date=op_date,
                notes=f"Reversa descargo factura {export_invoice.invoice_number} (header {header.id})",
            )
            db.add(ret_mov)
            db.flush()
            ret_mov.order_peps = ret_mov.id

        header.status = DischargeStatus.CANCELLED
        header.cancelled_by = cancelled_by
        header.cancellation_reason = (
            f"Des-actualización de factura de exportación {export_invoice.invoice_number}"
        )


def _set_invoice_unprocessed(invoice: InvoiceHeader, line_count: int) -> None:
    """
    Clarion mapping:
      EqiFex:ComofueProcesada=''
      EqiFex:Estatus='NA'
      EqiFex:Cant_Partidas = Loc:PartidasExpo
    """
    invoice.process_log = None
    invoice.status = InvoiceStatus.REVERSED
    invoice.party_count = line_count


def revert_process(
    db: Session,
    invoice: InvoiceHeader,
    lines: List[LineItem],
    tenant_id: str,
    company_id: str,
    errors: ErrorCollector,
    cancelled_by: Optional[str] = None,
) -> list:
    """
    Des-actualización de factura de exportación (paridad Clarion).

    Notes:
    - BEGIN/COMMIT/ROLLBACK SQL explícitos del Clarion se controlan con la
      transacción de SQLAlchemy en el task (commit/rollback externo).
    - Las rutinas Clarion invocadas con DO se dejan como TODO por ahora.
    """
    _ = (db, tenant_id, company_id)  # reserved for future TO DO implementations
    sql_errors: list = []

    # PROCESO DE REVERSIÓN
    _check_access_lock(invoice)

    # VERIFICAR SI HAY PARTIDAS DE EXPORTACION
    line_count = len(lines)

    # VALIDACION DEL CAMBIO DE REGIMEN
    _validate_regime_change_definitive_invoice_exists(db, invoice, errors)
    errors.raise_if_errors()

    # VALIDACIONES DE PARTIDAS NORMAL <> REPARACION
    if (invoice.invoice_type or "").upper() != "NODES":
        _return_discharged_quantities(db, invoice, lines)
        _unmark_returned_series(db, invoice, lines)
        _cancel_discharge_records(db, invoice, cancelled_by)

    _set_invoice_unprocessed(invoice, line_count)

    # Proceso finalizado correctamente
        
    # 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
