import logging

from celery import Task

from core.celery_app import celery_app
from core.database import CoreSessionLocal
from core.exceptions import ErrorCollector, ValidationException

from api.v1.modules.a76.invoices.models import InvoiceHeader
from api.v1.modules.a76.general_catalogs.company.models import Company
from .pre_validators import pre_validators
from .sub_process.review_classes import review_classes
from .sub_process.review_exchange_rate import review_exchange_rate
from .sub_process.review_weights import review_weights_kgs, review_weights_lbs
from .sub_process.review_rule_octave import valida_imp_regla_octava, descuenta_cupo_r_octava
from .sub_process.assing_values import assign_values_lines, assign_values_invoice
from .sub_process.assing_values_def_mex import (
    assign_values_iva_lines,
    assign_values_invoice_totals,
)
from ..balance.create_balance_entries import create_balance_entries
from .main_process import _validate_sisimp_limits, _update_invoice_totals, _validate_lines

logger = logging.getLogger(__name__)


def _progress(task: Task, current: int, status: str) -> None:
    task.update_state(state="PROGRESS", meta={"current": current, "status": status})


@celery_app.task(bind=True, name="process_invoice_task")
def process_invoice_task(self: Task, invoice_id: int, tenant_id: str, company_id: str) -> dict:
    """
    Procesa una factura de importación ejecutando todas las validaciones y
    actualizaciones del proceso principal (main_process) con reporte de progreso.
    """
    db = CoreSessionLocal()
    try:
        # ── Paso 1: Cargar factura ────────────────────────────────────────────
        _progress(self, 5, "Cargando factura...")
        invoice: InvoiceHeader | None = db.get(InvoiceHeader, invoice_id)
        if invoice is None:
            return {
                "status": "error",
                "message": f"Factura con id {invoice_id} no encontrada.",
                "errors": [],
            }

        errors = ErrorCollector()

        # ── Paso 2: Pre-validaciones ──────────────────────────────────────────
        _progress(self, 10, "Validando datos de la factura...")
        lines = pre_validators(db, invoice, tenant_id, company_id, errors)
        if not lines:
            errors.add_error(
                field="line_items",
                message="La factura debe contener al menos una partida para ser importada",
                solution=["Agregue partidas a la factura antes de intentar importarla"],
                code="NO_LINE_ITEMS",
            )
        errors.raise_if_errors()

        # ── Paso 3: Revisión clases, tipo de cambio y pesos ──────────────────
        _progress(self, 30, "Revisando clases y tipo de cambio...")
        review_classes(db, invoice, lines, tenant_id, company_id, errors)
        review_exchange_rate(db, invoice, errors)

        if invoice.logistics and invoice.logistics.weight_type == "kgs":
            review_weights_kgs(db, lines, tenant_id, company_id, errors)
        elif invoice.logistics and invoice.logistics.weight_type == "lbs":
            review_weights_lbs(db, lines, tenant_id, company_id, errors)

        # ── Paso 4: Asignación de valores ─────────────────────────────────────
        _progress(self, 50, "Calculando valores por partida...")
        raw_type = invoice.invoice_type
        invoice_type = (raw_type or "").strip().upper()
        logger.info(
            "celery import process invoice_type: invoice_id=%s raw=%r normalized=%r document_type=%r",
            invoice.id,
            raw_type,
            invoice_type,
            getattr(invoice, "document_type", None),
        )
        if invoice_type in {"DEF", "MEX"}:
            assign_values_iva_lines(invoice, lines)
            assign_values_invoice_totals(invoice, lines)
        else:
            assign_values_lines(invoice, lines)
            assign_values_invoice(invoice, lines)

        # ── Paso 5: Validaciones por partida ──────────────────────────────────
        _progress(self, 70, "Validando partidas...")
        octave_desc, octave_available = _validate_lines(
            db, invoice, lines, tenant_id, company_id, errors
        )

        # ── Paso 6: Regla Octava y límites SisImp ─────────────────────────────
        _progress(self, 85, "Validando cupos de Regla Octava...")
        company: Company | None = db.get(Company, invoice.company_id)
        if company and company.prosec and octave_desc:
            valida_imp_regla_octava(
                db=db,
                desc_list=octave_desc,
                dis_dict=octave_available,
                tenant_id=tenant_id,
                company_id=company_id,
                errors=errors,
            )
        _validate_sisimp_limits(invoice, errors)
        errors.raise_if_errors()

        # ── Paso 7: Descuento de cupos y actualización de totales ─────────────
        _progress(self, 95, "Actualizando totales...")
        sql_errors: list = []
        if octave_desc:
            descuenta_cupo_r_octava(
                db=db,
                desc_list=octave_desc,
                tenant_id=tenant_id,
                company_id=company_id,
                sql_errors=sql_errors,
            )
        _update_invoice_totals(invoice, lines)

        # ── Paso 8: Generar saldos en a24.balance_movement ───────────────────
        _progress(self, 98, "Generando saldos de inventario...")
        if invoice_type not in {"DEF", "MEX"}:
            create_balance_entries(db, invoice, lines)

        db.flush()
        db.commit()

        return {
            "status": "success",
            "invoice_id": invoice_id,
            "sql_errors": sql_errors,
        }

    except ValidationException as exc:
        db.rollback()
        return {
            "status": "validation_error",
            "message": exc.message,
            "errors": exc.errors,
        }
    except Exception as exc:
        db.rollback()
        raise exc
    finally:
        db.close()
