import logging
logger = logging.getLogger(__name__)
from datetime import date, datetime
from decimal import Decimal
from typing import List

from sqlalchemy.orm import Session

from api.v1.modules.a76.parts.models import Part
from api.v1.modules.a76.classes.models import Class
from api.v1.modules.a76.invoices.models import Currency, InvoiceHeader, InvoiceStatus
from api.v1.modules.a76.items.models import LineItem
from api.v1.modules.a76.items.common.fractions import search_fraction_preference
from api.v1.modules.a76.general_catalogs.company.models import Company
from core.exceptions import ErrorCollector
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_series import review_series
from .sub_process.review_rule_octave import (
    llena_impo_permiso_regla_octava,
    revpermiso_regla_octava,
    valida_imp_regla_octava,
    descuenta_cupo_r_octava,
)
from ...common.process.review_uma import revisa_uma
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 .sub_process.review_limits import review_limits

# Parameter Service
from api.v1.modules.a76.app_settings.service import AppSettingsService


def _validate_lines(
    db: Session,
    invoice: InvoiceHeader,
    lines: List[LineItem],
    tenant_id: str,
    company_id: str,
    errors: ErrorCollector,
) -> tuple[list, dict]:
    """Recorre cada partida y ejecuta las validaciones individuales."""
    company = db.get(Company, invoice.company_id)
    company_rfc = (company.rfc or "").strip().upper() if company else ""
    octave_available: dict = {}
    octave_desc: list = []
    
    for line in lines:
        if (line.financial and line.fa_data) and not line.fa_data.is_subitem and (line.financial.unit_cost_capture or Decimal(0)) == 0:
            errors.add_error(
                field=f"line[{line.line_number}].unit_cost_capture",
                message="No existe el costo unitario para la Partida.",
                solution=[f"Entrar a la partida: {line.line_number} y capturar el Costo Unitario."],
                code="UNIT_COST_REQUIRED",
            )

        if line.class_id is not None:
            cls: Class | None = db.get(Class, line.class_id)
            if cls and cls.is_active is False:
                errors.add_error(
                    field=f"line[{line.line_number}].class",
                    message=f"El número de parte: {cls.class_code} esta desactivado, no se pueden hacer movimientos.",
                    solution=["Seleccionar un número de parte activo."],
                    code="CLASS_DISABLED",
                )

        if line.part_number_id is not None:
            part: Part | None = db.get(Part, line.part_number_id)
            if part and part.is_active is False:
                errors.add_error(
                    field=f"line[{line.line_number}].part_number",
                    message=f"El número de parte: {part.part_number} esta desactivado, no se pueden hacer movimientos.",
                    solution=["Seleccionar un número de parte activo."],
                    code="PART_DISABLED",
                )

        search_fraction_preference(
            db=db,
            country=line.customs.origin_country or "",
            fraccion=line.customs.fraction if line.customs else "",
            fraction_type=line.customs.fraction_type if line.customs else "",
            sector=line.customs.sector if line.customs else "",
            invoice_date=invoice.invoice_date,
            errors=errors,
        )        
        
        review_series(db, line, company_rfc, tenant_id, company_id, errors)

        if line.octave_permit:
            if not company.prosec:
                errors.add_error(
                    field=f"line[{line.line_number}].octave_permit",
                    message="No se puede hacer uso de la Regla Octava...",
                    solution=["Borrar el permiso o dar de alta el permiso PROSEC..."],
                    code="OCTAVA_SIN_PROSEC",
                )
            else:
                desc_entry = llena_impo_permiso_regla_octava(
                    db=db, invoice=invoice, line=line, company_rfc=company_rfc,
                    tenant_id=tenant_id, company_id=company_id, errors=errors,
                )
                if desc_entry is not None:
                    octave_desc.append(desc_entry)
                available = revpermiso_regla_octava(
                    db=db, invoice=invoice, line=line, company_rfc=company_rfc,
                    tenant_id=tenant_id, company_id=company_id, errors=errors,
                )
                if available is not None:
                    key = (available.octave_permit, available.ro_line, available.country_code)
                    octave_available.setdefault(key, available)

        revisa_uma(db=db, line=line, tenant_id=tenant_id, company_id=company_id, errors=errors)

    return octave_desc, octave_available


def _validate_sisimp_limits(invoice: InvoiceHeader, settings: dict, errors: ErrorCollector) -> None:
    """Valida los límites de cantidad, peso y valor configurados en SisImpo/SisDef."""
    review_limits(invoice, settings, errors)


def _update_invoice_totals(invoice: InvoiceHeader, lines: List[LineItem]) -> None:
    """Actualiza totales, IVA e incrementables en la factura."""
    tc = Decimal(str(invoice.financials.exchange_rate or 0))
    tc_mm = Decimal(str(invoice.financials.exchange_rate_mm or 0))

    iva_factor = Decimal(str(invoice.financials.iva_factor or 0)) if invoice.financials.iva_factor else Decimal(0)
    if invoice.invoice_date and invoice.invoice_date >= date(2014, 12, 31):
        invoice.financials.iva_mn = float(Decimal(str(invoice.financials.value_mn or 0)) * iva_factor / 100)
        invoice.financials.iva_me = float(Decimal(str(invoice.financials.value_me or 0)) * iva_factor / 100)
    else:
        invoice.financials.iva_mn = 0.0
        invoice.financials.iva_me = 0.0

    freight = Decimal(str(invoice.financials.freight or 0))
    insurance = Decimal(str(invoice.financials.insurance or 0))
    packaging = Decimal(str(invoice.financials.packaging or 0))
    other = Decimal(str(invoice.financials.other_increments or 0))
    base_increm = freight + insurance + packaging + other

    if invoice.financials.currency == Currency.FOREIGN:
        val_seguro = Decimal(str(invoice.financials.total_increments_me or 0)) - base_increm
        invoice.financials.total_increments_me = float(base_increm + val_seguro)
        invoice.financials.total_increments_mn = float(Decimal(str(invoice.financials.total_increments_me)) * tc)
    elif invoice.financials.currency == Currency.LOCAL:
        val_seguro = Decimal(str(invoice.financials.total_increments_mn or 0)) - base_increm
        invoice.financials.total_increments_mn = float(base_increm + val_seguro)
        invoice.financials.total_increments_me = float((Decimal(str(invoice.financials.total_increments_mn)) / tc) if tc else Decimal(0))
    elif invoice.financials.currency == Currency.MANUAL:
        val_seguro = ((Decimal(str(invoice.financials.total_increments_me or 0)) / tc_mm) if tc_mm else Decimal(0)) - base_increm
        invoice.financials.total_increments_me = float((base_increm + val_seguro) * tc_mm)
        invoice.financials.total_increments_mn = float(Decimal(str(invoice.financials.total_increments_me)) * tc)

    invoice.status = InvoiceStatus.PROCESSED
    invoice.party_count = len(lines)

    if getattr(invoice, "_cal_val_base_tc", 0) == 1:
        invoice.process_log = "Se Actualizó con el Tipo de Cambio de la Fecha de Pago de Pedimento."
    if getattr(invoice, "_act_seguridad", 0) == 1 and hasattr(invoice, "_username"):
        invoice.updated_by = invoice._username


def main_process(db: Session, invoice: InvoiceHeader, tenant_id: str, company_id: str, username: str = "SYSTEM") -> dict:
    """Proceso principal para importar facturas."""
    errors = ErrorCollector()
    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", solution=["Agregue partidas"], code="NO_LINE_ITEMS")
    errors.raise_if_errors()

    settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id))
    
    inv_type = (invoice.invoice_type or "").strip().upper()
    op_type = "imp"
    
    # Hierarchical resolve: invoices.types.imp.{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("calvalbasetcped") or form_data.get("CalValBaseTCPed") or 0)
    act_seguridad = int(form_data.get("actseguridad") or form_data.get("ActSeguridad") or 0)
    logger.info(f"AUDIT_DEBUG: act_seguridad resolve result = {act_seguridad} for Invoice={invoice.invoice_number}")

    invoice._cal_val_base_tc = cal_val_base_tc
    invoice._act_seguridad = act_seguridad
    invoice._username = username

    exchange_rate = invoice.financials.exchange_rate if invoice.financials else 0
    which_exchange_rate = "TCFAC"

    if cal_val_base_tc == 1:
        if invoice.compliance_mx and invoice.compliance_mx.pedimento:
            pedimento = invoice.compliance_mx.pedimento
            if pedimento.pedimento_dates and pedimento.pedimento_dates.payment_date:
                payment_date = pedimento.pedimento_dates.payment_date
                from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate
                from sqlalchemy import select
                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:
                    exchange_rate = float(ex_rate_row.value)
                    which_exchange_rate = "TCPED"
                else:
                    errors.add_error(field="exchange_rate", message=f"No se encontró tipo de cambio para {payment_date.date()}.", solution=["Capturar TC en catálogos."], code="EXCHANGE_RATE_NOT_FOUND")
            else:
                errors.add_error(field="pedimento", message="El pedimento no tiene fecha de pago.", solution=["Capturar fecha de pago."], code="PEDIMENTO_NO_PAYMENT_DATE")
        else:
            errors.add_error(field="compliance_mx.pedimento", message="Se requiere pedimento para el TC.", solution=["Asignar pedimento."], code="PEDIMENTO_REQUIRED_FOR_TC")

    if invoice.compliance_mx: invoice.compliance_mx.which_exchange_rate = which_exchange_rate
    if invoice.financials: invoice.financials.exchange_rate = exchange_rate
    db.flush()

    review_classes(db, invoice, lines, tenant_id, company_id, errors)
    review_exchange_rate(db, invoice, cal_val_base_tc, 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)    

    invoice_type = (invoice.invoice_type or "").strip().upper()   
    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)

    octave_desc, octave_available = _validate_lines(db, invoice, lines, tenant_id, company_id, errors)
    company = db.get(Company, invoice.company_id)
    if 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, settings, errors)
    errors.raise_if_errors()

    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)
    if invoice_type not in {"DEF", "MEX"}:
        create_balance_entries(db, invoice, lines)

    db.flush()
    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="ACTUALIZAR FACTURA", movement="IMPORTACION",
            username=username, tenant_id=int(tenant_id), company_id=int(company_id),
            table_name="invoice_header", record_id=str(invoice.id), operation_type="UPDATE"
        )

    db.flush()
    return {"status": "success", "invoice_id": str(invoice.id)}
