import shutil
import base64
import pdfkit
from pathlib import Path
from decimal import Decimal
from typing import Tuple, List, Callable, Optional

from jinja2 import Environment, FileSystemLoader, select_autoescape
from fastapi import HTTPException
from sqlalchemy.orm import Session

# --- MODELOS ---
from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceLogistics, InvoiceComplianceMx 
from api.v1.modules.a76.items.line_financials.models import LineFinancial
from api.v1.modules.a76.items.line_quantities.models import LineQuantity
from api.v1.modules.a76.clients_and_providers.models import (
    ClientProvider, ClientProviderAddress, ClientProviderPrograms
)
from api.v1.modules.a76.parts.models import Part
from api.v1.modules.a76.pedmientos.models import Pedimentos
from api.v1.modules.a76.general_catalogs.company.models import Company
from api.v1.modules.a76.customs_brokers.models import CustomsBroker
from api.v1.modules.a76.items.models import LineItem 

# --- TRANSPORTATION MODELS ---
from api.v1.modules.a76.transportation.transporters.models import Transporter
from api.v1.modules.a76.transportation.vehicles.models import Vehicle
from api.v1.modules.a76.transportation.trailers.models import Trailer
from api.v1.modules.a76.transportation.drivers.models import Driver

# --- MODELO DE FRACCIONES ---
from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import TariffFraction

# --- SCHEMAS ---
from ...mex.schemas import (
    ClienteSchema, PartidaSchema, TotalesSchema, 
    FacturaSchema, FacturaImportacionCompleta
)

class ConsolidadoImportacionMexService:
    def __init__(self):
        self.template_dir = Path(__file__).parent.parent / "templates"
        self.jinja_env = Environment(
            loader=FileSystemLoader(self.template_dir),
            autoescape=select_autoescape(['html', 'xml'])
        )
        self.template = self.jinja_env.get_template('cons_mex_ver.html')

    def _get_wkhtmltopdf_config(self):
        path = shutil.which("wkhtmltopdf") or "/usr/local/bin/wkhtmltopdf"
        if not Path(path).exists():
            raise RuntimeError("wkhtmltopdf no encontrado.")
        return pdfkit.configuration(wkhtmltopdf=path)

    def formatear_numero(self, valor, decimales: int = 2):
        if valor is None: return 0.0
        try:
            return round(float(valor), decimales)
        except: return 0.0

    def _format_fraccion_fallback(self, fraccion_raw: str) -> str:
        if not fraccion_raw or len(fraccion_raw) < 8:
            return fraccion_raw
        return f"{fraccion_raw[:4]}.{fraccion_raw[4:6]}.{fraccion_raw[6:]}"

    def _obtener_datos_cliente(self, db: Session, client_id: int, rol: str) -> ClienteSchema:
        main = db.query(ClientProvider).filter(ClientProvider.id == client_id).first()
        if not main:
            return ClienteSchema(header=rol, nombre="Desconocido", direccion="", tax_id="", codigo_postal="", ciudad="", estado="", pais="MEX")
        
        addr = db.query(ClientProviderAddress).filter(ClientProviderAddress.client_id == client_id).first()
        prog = db.query(ClientProviderPrograms).filter(ClientProviderPrograms.client_id == client_id).first()

        return ClienteSchema(
            header=rol,
            nombre=(main.name or main.short_name) or "S/N",
            direccion=(addr.streets or "") if addr else "", 
            num_exterior=(addr.exterior_number or "") if addr else "",
            num_interior=(addr.interior_number or "") if addr else "",
            colonia=(addr.neighborhood or "") if addr else "",
            codigo_postal=(addr.postal_code or "") if addr else "",
            ciudad=(addr.city or "") if addr else "",
            estado=(addr.state or "") if addr else "",
            pais=(addr.country or "MEX") if addr else "MEX",
            tax_id=getattr(main, "rfc", "") or "",
            programa="IMMEX" if (prog and prog.program) else "", 
            autorizacion=prog.program_number if prog else "",
            prosec=prog.prosec_authorization if (prog and prog.prosec and prog.prosec_authorization) else "",
            reg_emp=prog.val_certified_company_registry if (prog and hasattr(prog, 'val_certified_company_registry')) else (
                prog.certified_company_registry if (prog and prog.certified_company_registry) else ""
            ),
            cert=prog.is_certified_company if (prog and prog.is_certified_company) else "" 
        )

    def obtener_datos(self, db: Session, invoice_id: int, company_id: int, progress_callback: Optional[Callable] = None) -> FacturaImportacionCompleta:
        try:
            if progress_callback: progress_callback(10, "Buscando factura...")
            header = db.query(InvoiceHeader).filter(InvoiceHeader.id == invoice_id, InvoiceHeader.company_id == company_id).first()
            if not header: raise HTTPException(status_code=404, detail="Factura no encontrada")

            compliance = header.compliance_mx 
            logistics = header.logistics if header.logistics else None
            financials = header.financials if header.financials else None
            if progress_callback: progress_callback(20, "Obteniendo datos de pedimento...")
            pedimento_id = compliance.pedimento_id if (compliance and compliance.pedimento_id) else header.related_doc_id
            pedimento = db.query(Pedimentos).filter(Pedimentos.id == pedimento_id).first() if pedimento_id else None
            
            if progress_callback: progress_callback(30, "Obteniendo cliente y proveedor...")
            proveedor_id = compliance.provider_id if compliance else None
            cliente_proveedor = self._obtener_datos_cliente(db, proveedor_id, "Proveedor / supplier:") if proveedor_id else ClienteSchema(header="Proveedor", nombre="No Asignado", direccion="", tax_id="", codigo_postal="", ciudad="", estado="", pais="")

            nombre_agente = ""
            if compliance and compliance.customs_broker_id:
                broker = db.query(CustomsBroker).filter(CustomsBroker.id == compliance.customs_broker_id).first()
                if broker: nombre_agente = broker.name

            company = db.query(Company).filter(Company.id == header.company_id).first()
            # Datos Default (Company/Importer) - Used for fallback or Right Side (Enviado A)
            # Default Header (Company)
            cliente_default = ClienteSchema(
                header="Importer / Consignee:",
                nombre=getattr(company, 'name', "Empresa Local"),
                direccion="DOMICILIO FISCAL",
                num_exterior="", colonia="", codigo_postal="", ciudad="", estado="", pais="MEX",
                tax_id=getattr(company, 'rfc', ""),
                programa=getattr(company, 'program', "IMMEX"), autorizacion=getattr(company, 'program_number', "")
            )

            # Left Side Logic (Consignatario / Sold To)
            cliente_vendido = cliente_default
            if compliance and compliance.sold_to_id:
                # Map known headers or default to Sold To / Vendido a
                raw = (compliance.sold_to_header or "").upper()
                if "CONSIGN" in raw:
                    clean_header = "Consignee / Consignatario:"
                else:
                    clean_header = "Sold To / Vendido a:"
                
                cliente_vendido = self._obtener_datos_cliente(db, compliance.sold_to_id, clean_header)
            
            # Right Side Logic (Enviado A / Shipped To)
            cliente_enviado = cliente_default
            if compliance and compliance.shipped_to_id:
                # Map to Shipped To / Enviado a
                clean_header_shipped = "Shipped To / Enviado a:"
                
                # Fetch client data
                cliente_enviado = self._obtener_datos_cliente(db, compliance.shipped_to_id, clean_header_shipped)

            remesa_valor = str(compliance.remesa) if (compliance and compliance.remesa) else ""
            acuse_valor = str(compliance.edocument) if (compliance and compliance.edocument) else "N/A"

            patente_val = ""
            if pedimento and pedimento.license:
                patente_val = pedimento.license
            elif 'broker' in locals() and broker and broker.license:
                patente_val = broker.license


            # --- Transport Data Fetching ---
            transporte_txt = str(logistics.transport_type) if (logistics and logistics.transport_type) else ""
            num_transporte_val = (logistics.trailer_num or "") if logistics else ""
            
            # Init values
            placas_val = (logistics.license_plate or "") if logistics else "" # Placas Tracto
            placas_remolque_val = ""
            transportista_val = (logistics.carrier_id or "") if logistics else ""
            caat_val = ""
            scac_val = ""
            licencia_cond_val = ""
            conductor_nombre = ""

            # Block Logic (Clarion Style) for transportista_info
            transport_lines = []

            if logistics:
                # 1. Transporter (CAAT / SCAC)
                if logistics.carrier_int_id is not None:
                    transporter_obj = (
                        db.query(Transporter)
                        .filter(Transporter.transporter_id == logistics.carrier_int_id)
                        .first()
                    )
                elif logistics.carrier_id:
                    transporter_obj = db.query(Transporter).filter(
                        Transporter.transporter_key == logistics.carrier_id
                    ).first()
                    if transporter_obj:
                        caat_val = transporter_obj.caat_code or ""
                        scac_val = transporter_obj.transport_code or "" # Mapping transport_code to SCAC
                        transportista_val = transporter_obj.name or logistics.carrier_id

                        # Clarion Logic: Name first
                        # Line 1: Name
                        transport_lines.append(transporter_obj.name or "")
                        
                        # Line 2: Streets
                        if transporter_obj.streets:
                            transport_lines.append(transporter_obj.streets)
                        
                        # Line 3: City, State, Country
                        loc_line = ""
                        if transporter_obj.city:
                             loc_line = transporter_obj.city
                             if transporter_obj.state:
                                 loc_line += f", {transporter_obj.state}, "
                             else:
                                 loc_line += ", "
                        else:
                             if transporter_obj.state:
                                 loc_line = f"{transporter_obj.state},"
                        
                        country_desc = transporter_obj.country or "" 
                        if loc_line:
                            loc_line += f" {country_desc}"
                        elif country_desc:
                            loc_line = country_desc
                        
                        if loc_line.strip(", "):
                            transport_lines.append(loc_line)

                # 2. Vehicle (Placas Tracto) - Try transport_id first
                if logistics.transport_id:
                    if logistics.transport_int_id is not None:
                        veh_obj = (
                            db.query(Vehicle)
                            .filter(Vehicle.vehicle_id == logistics.transport_int_id)
                            .first()
                        )
                    else:
                        veh_obj = db.query(Vehicle).filter(
                            Vehicle.vehicle_key == logistics.transport_id
                        ).first()
                    if veh_obj:
                         placas_val = veh_obj.plate_number or placas_val
                elif logistics.vehicle_num: # Fallback to vehicle_num if populated and transport_id failed/empty
                     veh_obj = db.query(Vehicle).filter(Vehicle.vehicle_key == logistics.vehicle_num).first()
                     if veh_obj:
                         placas_val = veh_obj.plate_number or placas_val

                # 3. Trailer (Placas Remolque)
                if logistics.trailer_num:
                    if logistics.trailer_int_id is not None:
                        trl_obj = (
                            db.query(Trailer)
                            .filter(Trailer.trailer_id == logistics.trailer_int_id)
                            .first()
                        )
                    else:
                        trl_obj = db.query(Trailer).filter(
                            Trailer.trailer_number == logistics.trailer_num
                        ).first()
                    if trl_obj:
                        placas_remolque_val = trl_obj.plate_number or ""

                # 4. Driver (License)
                if logistics.carrier_id and logistics.driver_name:
                    conductor_nombre = logistics.driver_name
                    # Attempt to find driver by name + carrier
                    drv_obj = db.query(Driver).filter(
                        Driver.transporter_key == logistics.carrier_id,
                        Driver.driver_name == logistics.driver_name
                    ).first()
                    if drv_obj:
                         licencia_cond_val = drv_obj.license_number or ""
                
                # --- Building the rest of the block ---
                
                # Line 4: Driver
                if conductor_nombre:
                     transport_lines.append(f"Driver/Conductor: {conductor_nombre}")
                
                # Line 5: Conveyance / Transporte
                t_label = "Conveyance / Transporte"
                t_val = placas_val # Default to Truck Plate
                
                if logistics.transport_type:
                    ttype = str(logistics.transport_type).lower()
                    if "caja" in ttype or "trailer" in ttype:
                        t_label = "Trailer / Caja"
                        t_val = placas_remolque_val or num_transporte_val
                    elif "placa" in ttype:
                         t_label = "Plates / Placas"
                    elif "camion" in ttype or "truck" in ttype:
                         t_label = "Truck / Camión"
                
                if t_val:
                     transport_lines.append(f"{t_label}: {t_val}")
                
                # Line 6: SCAC / CAAT
                codes_line = ""
                if scac_val:
                    codes_line = f"SCAC Code/Clave: {scac_val}"
                if caat_val:
                    if codes_line:
                        codes_line += f", CAAT Code/Clave: {caat_val}"
                    else:
                        codes_line = f"CAAT Code/Clave: {caat_val}"
                
                if codes_line:
                     transport_lines.append(codes_line)

            # Join with newlines
            transport_block_str = "\n".join([l for l in transport_lines if l])

            factura_schema = FacturaSchema(
                numero=header.invoice_number or "S/N",
                fecha=str(header.invoice_date) if header.invoice_date else "",
                tipo_cambio=float(financials.exchange_rate) if (financials and financials.exchange_rate) else (float(pedimento.exchange_rate) if pedimento and pedimento.exchange_rate else 1.0),
                moneda=getattr(header, 'currency', "USD") or "USD",
                incoterm=(logistics.incoterm or "") if logistics else "",
                observaciones=header.observation_es or header.observation_en or "",
                pedimento=f"{pedimento.year}  {pedimento.customs_office[:2] if pedimento.customs_office else ''}  {pedimento.license}  {pedimento.pedimento_number}" if pedimento else "",
                clave_pedimento=pedimento.pedimento_code if pedimento else "",
                regimen=header.document_type or "",
                patente=patente_val,
                agente_aduanal=nombre_agente,
                transporte=transporte_txt,
                num_transporte=num_transporte_val,
                placas=placas_val,
                placas_remolque=placas_remolque_val,
                transportista=transportista_val,
                caat=caat_val,
                scac=scac_val,
                licencia_conductor=licencia_cond_val,
                aduana=compliance.aduana if (compliance and compliance.aduana) else (pedimento.customs_office[:2] if (pedimento and pedimento.customs_office) else ""),
                precinto=(logistics.seal_number or "") if logistics else "",
                destino=(logistics.destination_goods or "") if logistics else "",
                remesa=remesa_valor, acuse_electronico=acuse_valor,
                representante_legal=getattr(company, 'responsible', "") or "",
                nombre_empresa=getattr(company, 'name', "") or "",
                transportista_info=transport_block_str
            )
            
            if progress_callback: progress_callback(50, "Procesando partidas...")
            
            # --- Fetch Lines from SINGLE Invoice (Requested Scope Change) ---
            # User requested to ONLY report items from the specific selected invoice,
            # NOT consolidating all invoices from the same Pedimento.
            target_invoice_ids = [header.id]
            
            lines = db.query(LineItem).filter(
                LineItem.invoice_id.in_(target_invoice_ids)
            ).all()

            partidas_list = []
            
            # --- AGGREGATION LOGIC (Refactoring based on Clarion) ---
            from collections import defaultdict
            # Key: (us_fraction_code, origin_country)
            # Value: Object with accumulated fields
            aggregated_data = defaultdict(lambda: {
                "qty": 0.0, 
                "net_weight_kgs": 0.0, 
                "gross_weight_kgs": 0.0, 
                "total_value": 0.0,
                "est_total_value": 0.0,
                "description": "",
                "advalorem_txt": "0%",
                "unit_measure": "PZA", # Placeholder, takes first one found
                "hts_code_print": "",
                "part_number_display": "CONSOLIDADO"
            })

            # Pre-fetch US Tariff Fractions for efficiency if possible, or query inside loop (caching recommended)
            # For simplicity in this step, we query inside or rely on Part data. 
            # Ideally fetch USTariffFraction from DB based on Part.us_fraction

            # --- Optimización: Cargar Facturas en Memoria ---
            invoices_list = db.query(InvoiceHeader).filter(InvoiceHeader.id.in_(target_invoice_ids)).all()
            invoice_map = {inv.id: inv for inv in invoices_list}

            from api.v1.modules.sitar.fracciones_usa.catalog_resolve import (
                american_fraction_ad_valorem_from_row,
                resolve_american_fraction_from_sitar,
            )

            for line in lines:
                qty = db.query(LineQuantity).filter(LineQuantity.item_line_id == line.id).first()
                fin = db.query(LineFinancial).filter(LineFinancial.item_line_id == line.id).first()
                part_master = db.query(Part).filter(Part.id == line.part_number_id).first()
                
                # --- Resolver Identificadores (MOVED INSIDE MAIN LOOP) ---
                us_fraction_raw = ""
                origin_final = "MEX"
                
                if part_master:
                    origin_final = part_master.fa_data.origin_country if (part_master.fa_data and part_master.fa_data.origin_country) else "MEX"
                    us_fraction_raw = part_master.us_fraction if part_master.us_fraction else ""
                
                # Key for aggregation
                us_frac_clean = us_fraction_raw.strip()
                agg_key = (us_frac_clean, origin_final)
                # --- Weights & Qty ---
                q_line = float(qty.quantity) if (qty and qty.quantity) else 0.0
                nw_line = float(qty.net_weight) if qty else 0.0
                gw_line = float(qty.gross_weight) if qty else 0.0
                
                # --- Multi-Currency Normalization Logic ---
                # Determine Line Currency context
                # Use manual lookup instead of specific attribute
                invoice_id = line.invoice_id 
                line_invoice = invoice_map.get(invoice_id) if invoice_id else None
                
                line_currency_is_mxn = False
                line_exchange_rate = 1.0

                if line_invoice and line_invoice.financials:
                    # Check explicit currency string AND code
                    curr_desc = str(line_invoice.financials.currency or "").upper()
                    curr_code = str(line_invoice.financials.currency_type or "").upper()
                    
                    # Logic: It is MXN if description says PESO/MX or code is MXN/MN
                    is_mx_desc = ("MX" in curr_desc or "PESO" in curr_desc)
                    is_mx_code = ("MXN" in curr_code or "MN" == curr_code)
                    
                    # But if code allows clarifying USD, prioritize that
                    is_usd_code = ("USD" in curr_code)
                    
                    if is_usd_code:
                        line_currency_is_mxn = False
                    elif is_mx_code or is_mx_desc:
                        line_currency_is_mxn = True
                    else:
                        line_currency_is_mxn = False # Default to Foreign/USD if unsure

                    line_exchange_rate = float(line_invoice.financials.exchange_rate or 1.0)
                
                # Target Report Currency
                report_is_mxn = (factura_schema.moneda == 'MXN')

                # DEBUG LOGGING
                if line_invoice:
                     print(f"DEBUG: Line {line.id} - Inv {line_invoice.id} - CurrDesc: '{curr_desc}' Code: '{curr_code}' - Rate: {line_exchange_rate}")
                     print(f"DEBUG: Is MXN Context? {line_currency_is_mxn}. Report is MXN? {report_is_mxn}")

                # --- Get Financials for Line (Raw) ---
                v_total_raw = 0.0
                v_unitario_raw = 0.0

                if fin:
                    # NEW PRIORITY LOGIC (To avoid Inflation from dirty Customs Unit Cost)
                    # Priority 1: Use 'fin.value_usd' if it exists and > 0.
                    # Priority 2: Use 'fin.total_commercial_value' if it exists and > 0.
                    # Priority 3: Calculate using 'fin.unit_cost_commercial_usd' * 'q_line'.
                    # Priority 4: Only use 'fin.unit_cost_usd' * 'q_line' if commercial data is also missing.

                    val_usd = float(fin.value_usd or 0.0)
                    total_comm = float(fin.total_commercial_value or 0.0)
                    unit_comm_usd = float(fin.unit_cost_commercial_usd or 0.0)
                    unit_usd = float(fin.unit_cost_usd or 0.0)
                    
                    # 1. Direct Total: Custom Value (Best case)
                    if val_usd > 0:
                        v_total_raw = val_usd
                    
                    # 2. Direct Total: Commercial Total
                    elif total_comm > 0:
                        # Convert if invoice currency is MXN
                        if line_currency_is_mxn and line_exchange_rate > 0:
                            v_total_raw = total_comm / line_exchange_rate
                        else:
                            v_total_raw = total_comm
                            
                    # 3. Calc from Commercial Unit Cost (Safe Fallback)
                    elif unit_comm_usd > 0 and q_line > 0:
                        v_total_raw = unit_comm_usd * q_line
                        
                    # 4. Calc from Customs Unit Cost (Unknown Risk - Last Resort)
                    elif unit_usd > 0 and q_line > 0:
                        v_total_raw = unit_usd * q_line
                    
                    else:
                        v_total_raw = 0.0
                        
                    # NOTE: v_unitario_raw is left as 0.0 here.
                    # It will be calculated in the 'Calculation Gap Fill' block below:
                    # v_unitario_raw = v_total_raw / q_line
                    # This guarantees consistency and avoids the inflated unit cost record (198.00).

                # --- Calculation Gap Fill (Raw) ---
                if q_line > 0:
                    if v_total_raw == 0 and v_unitario_raw > 0:
                        v_total_raw = v_unitario_raw * q_line
                    if v_unitario_raw == 0 and v_total_raw > 0:
                        v_unitario_raw = v_total_raw / q_line

                # --- Conversion to Report Currency (DISABLED TEMPORARILY) ---
                # User confirms all are USD. Forcing direct sum to avoid logic errors in detection.
                v_total_line = v_total_raw
                v_unitario_line = v_unitario_raw

                # if report_is_mxn and not line_currency_is_mxn:
                #     # USD -> MXN
                #     v_total_line = v_total_raw * line_exchange_rate
                #     v_unitario_line = v_unitario_raw * line_exchange_rate
                # elif not report_is_mxn and line_currency_is_mxn:
                #     # MXN -> USD
                #     if line_exchange_rate > 0:
                #         v_total_line = v_total_raw / line_exchange_rate
                #         v_unitario_line = v_unitario_raw / line_exchange_rate
                #     else:
                #         v_total_line = 0.0
                #         v_unitario_line = 0.0
                
                print(f"DEBUG: ValRaw: {v_total_raw} -> ValFinal: {v_total_line}")
                
                # --- Resolve Fraction Details (Description & Rate) ---
                # Only if this is the first time we see this key (or overwrite, doesn't matter much as they should be same for same HTS)
                # We check if we already have description set to avoid re-querying if we want optimization, 
                # but relying on DB query per distinct fraction is safer.
                
                current_agg = aggregated_data[agg_key]
                
                if not current_agg["description"]:
                    resolved = (
                        resolve_american_fraction_from_sitar(us_frac_clean)
                        if us_frac_clean
                        else None
                    )
                    if resolved:
                        sitar_row, _canon = resolved
                        current_agg["description"] = (
                            (sitar_row.DESCRIPCION or "").strip() or "Sin Descripción"
                        )
                        adv_val = american_fraction_ad_valorem_from_row(sitar_row)
                        current_agg["advalorem_txt"] = (
                            f"{adv_val}%" if adv_val is not None else "0%"
                        )
                    else:
                         current_agg["description"] = part_master.description_spanish if part_master else "S/D"

                    current_agg["hts_code_print"] = us_frac_clean
                    current_agg["unit_measure"] = qty.weight_unit if qty else "KGS" # Default to first found

                # --- Calculate Estimated Tax for this Line ---
                rate = 0.0
                try:
                    clean_adv = current_agg["advalorem_txt"].replace("%", "").strip()
                    rate = float(clean_adv) / 100.0
                except: rate = 0.0
                
                v_est_line = v_total_line * rate
                
                # --- Accumulate ---
                current_agg["qty"] += q_line
                current_agg["net_weight_kgs"] += nw_line
                current_agg["gross_weight_kgs"] += gw_line
                current_agg["total_value"] += v_total_line
                current_agg["est_total_value"] += v_est_line


            # --- Convert Aggregated Data to Schema List ---
            partidas_list = []
            
            for (hts, origin), data in aggregated_data.items():
                
                # Calculate Unit Price based on Total Value / Total Qty
                unit_price = 0.0
                if data["qty"] > 0:
                    unit_price = data["total_value"] / data["qty"]
                
                partidas_list.append(PartidaSchema(
                    numero_parte="VARIOS", # Or empty
                    descripcion=data["description"],
                    fraccion=data["hts_code_print"],
                    origen=origin,
                    advalorem=data["advalorem_txt"],
                    preferencia="General",
                    cantidad_importacion=self.formatear_numero(data["qty"]),
                    unidad_medida=data["unit_measure"],
                    cantidad_bultos=0, # Summing bultos might be tricky if not homogeneous, check logic later
                    clave_bultos="",
                    peso_neto=self.formatear_numero(data["net_weight_kgs"]),
                    peso_bruto=self.formatear_numero(data["gross_weight_kgs"]),
                    valor_costo_unitario=self.formatear_numero(unit_price),
                    valor_total=self.formatear_numero(data["total_value"]),
                    valor_estimado=self.formatear_numero(data["est_total_value"])
                ))
            
            # Sort by Fraction (HTS Code)
            partidas_list.sort(key=lambda x: x.fraccion)

            totales = self.calcular_totales(partidas_list, Decimal(factura_schema.tipo_cambio))

            return FacturaImportacionCompleta(
                cliente_proveedor=cliente_proveedor, cliente_vendido=cliente_vendido,
                cliente_enviado=cliente_enviado, factura=factura_schema,
                partidas=partidas_list, totales=totales
            )

        except Exception as e:
            print(f"Error Service A76: {e}")
            raise HTTPException(status_code=500, detail=f"Error: {str(e)}")

    def calcular_totales(self, partidas: List[PartidaSchema], tipo_cambio: Decimal) -> TotalesSchema:
        cant = sum(p.cantidad_importacion for p in partidas)
        valor = sum(p.valor_total for p in partidas)
        peso_n = sum(p.peso_neto for p in partidas)
        peso_b = sum(p.peso_bruto for p in partidas)
        bultos = sum(p.cantidad_bultos for p in partidas)
        claves = [p.clave_bultos for p in partidas if p.clave_bultos]
        clave_comun = max(set(claves), key=claves.count) if claves else ""
        if bultos > 1 and clave_comun and not clave_comun.endswith("S"): clave_comun += "S"
        v_est = sum(p.valor_estimado for p in partidas if isinstance(p.valor_estimado, (int, float, Decimal)))
        
        tc = float(tipo_cambio) if tipo_cambio else 1.0
        return TotalesSchema(   
            cantidad_total=self.formatear_numero(cant), bultos_total=bultos, clave_bultos=clave_comun,
            peso_neto_total=self.formatear_numero(peso_n), peso_bruto_total=self.formatear_numero(peso_b),
            valor_total_total=self.formatear_numero(valor), valor_total_dolares=self.formatear_numero(valor / tc if tc > 0 else 0),
            valor_estimado_total=self.formatear_numero(v_est)
        )

    def generar_factura_completa(self, db: Session, invoice_id: int, company_id: int, formato: str = "pdf", progress_callback: Optional[Callable] = None) -> Tuple[bytes, str, str]:
        if progress_callback: progress_callback(5, "Iniciando servicio de reporte...")
        datos = self.obtener_datos(db, invoice_id, company_id, progress_callback)
        
        if progress_callback: progress_callback(80, "Renderizando plantilla...")
        
        # LOGO LOGIC
        logo_b64 = None
        try:
            # Fetch company to get logo path

            comp_logo = db.query(Company).filter(Company.id == company_id).first()
            if comp_logo and comp_logo.logo:
                p = Path(comp_logo.logo)
                
                # Logic robusta de búsqueda (igual que en routes.py)
                target_path = p
                if not target_path.exists():
                    # Intentar en la ruta estándar: app_data/logos/{id}/{nombre}
                    # Esto cubre el caso donde solo se guardó el nombre del archivo o la ruta absoluta cambió
                    fallback = Path(f"app_data/logos/{company_id}") / p.name
                    if fallback.exists():
                        target_path = fallback

                if target_path.exists():
                    with open(target_path, "rb") as image_file:
                        encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
                        # Detect MIME type loosely
                        mime = "image/png"
                        if target_path.suffix.lower() in ['.jpg', '.jpeg']: mime = "image/jpeg"
                        logo_b64 = f"data:{mime};base64,{encoded_string}"
        except Exception as e:
            print(f"Error loading logo: {e}")

        context = {
            'cliente_proveedor': datos.cliente_proveedor.model_dump(), 'cliente_vendido': datos.cliente_vendido.model_dump(),
            'cliente_enviado': datos.cliente_enviado.model_dump(), 'factura': datos.factura.model_dump(),
            'partidas': [p.model_dump() for p in datos.partidas], 'totales': datos.totales.model_dump(),
            'logo_b64': logo_b64
        }
        html_content = self.template.render(**context)
        nombre = f"Consolidado_{datos.factura.numero}.{formato}"
        if formato == "html": return html_content.encode('utf-8'), nombre, "text/html"
        
        if progress_callback: progress_callback(90, "Generando PDF final...")
        options = {'page-size': 'Letter', 'margin-top': '0.5in', 'margin-right': '0.5in', 'margin-bottom': '0.5in', 'margin-left': '0.5in', 'encoding': "UTF-8", 'enable-local-file-access': None}
        pdf = pdfkit.from_string(html_content, False, options=options, configuration=self._get_wkhtmltopdf_config())
        
        if progress_callback: progress_callback(100, "Completado")
        return pdf, nombre, "application/pdf"
