from typing import List, Dict, Any, Tuple
from sqlalchemy.orm import Session, joinedload
from datetime import datetime

from .schemas import Mainx30GenerationRequest, ErrorValidacion

# --- MODELOS A76 ---
from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceComplianceMx
from api.v1.modules.a76.items.models import LineItem
from api.v1.modules.a76.clients_and_providers.models import ClientProvider

class ScaiiProcessor:
    def __init__(self):
        self.cuenta_partidas = 0
        self.cuenta_facturas = 0
        self.valor_total_factura = 0.0
        self.flete_total = 0.0
        self.peso_bruto_factura = 0.0
        self.peso_neto_factura = 0.0
        self.errores: List[ErrorValidacion] = []

    def _obtener_datos_cliente(self, cliente: ClientProvider) -> dict:
        """Extrae de manera segura los datos del cliente/dirección"""
        address = cliente.address
        pais_raw = (address.country or "MX").upper() if address else "MX"
        
        pais = "MX"
        if pais_raw in ["MEXICO", "MEX", "MX"]:
            pais = "MX"
        elif pais_raw in ["USA", "US", "UNITED STATES"]:
            pais = "US"
        else:
            pais = pais_raw[:2]
        
        tax_id = cliente.rfc or ""

        data = {
            "nombre": (cliente.name or "")[:39],
            "tax_id": tax_id[:15],
            "broker": "", "calle": "", "cp": "", "ciudad": "", "estado": "", "pais": pais, "tel": "",
            "manufacturer_id": "", "tipo_ext_nac": "E"
        }
        
        # TipoExtNac logic from Clarion: N (National/ManufacturerID) or E (External/TaxID)
        # We'll use TaxID as default for definitive if not specified
        data["manufacturer_id"] = (cliente.programs.manufacturer_id or "")[:15] if cliente.programs else ""
        
        if cliente.programs:
            data["broker"] = (cliente.programs.broker or "")[:6]
            
        if address:
            calle_base = address.streets or ""
            num_base = address.exterior_number or ""
            # Prevent 'None' string
            calle_comp = f"{calle_base} {num_base}".strip()
            
            # Clarion expects 20 chars for city_state: 5 CP + 11 City + 4 State
            cp_formatted = (address.postal_code or "")[:5]
            city_formatted = (address.city or "")[:11]
            state_formatted = (address.state or "")[:4]
            data["city_state"] = f"{cp_formatted:<5}{city_formatted:<11}{state_formatted:<4}"
            data["calle"] = calle_comp[:35]
            data["cp"] = (address.postal_code or "")[:9]
            data["ciudad"] = (address.city or "").strip()[:20]
            data["estado"] = (address.state or "").strip()[:2].upper()
            data["tel"] = (address.phone or "")[:15]
            
        return data

    def _agregar_error(self, partida, id_err, desc, sol, tipo):
        self.errores.append(ErrorValidacion(partida=partida, linea=0, descripcion=f"[ {id_err} ] {desc}", soluciones=sol, identificador=tipo))

class ScafDefProcessor(ScaiiProcessor):
    """Procesador para Importación Definitiva basado en lógica Clarion (SComprasMexID)"""
    def __init__(self):
        super().__init__()

    def procesar_facturas(
        self, db: Session, facturas_nums: List[str], empresa_dict: Dict[str, Any], request: Mainx30GenerationRequest
    ) -> Tuple[List[str], List[ErrorValidacion]]:
        lineas = []
        self.errores = []
        
        # 1. Traer Facturas por número
        facturas = db.query(InvoiceHeader).join(
            InvoiceComplianceMx, InvoiceHeader.id == InvoiceComplianceMx.invoice_id
        ).options(
            joinedload(InvoiceHeader.financials),
            joinedload(InvoiceHeader.compliance_mx),
            joinedload(InvoiceHeader.logistics)
        ).filter(
            InvoiceHeader.invoice_number.in_(facturas_nums)
        ).all()
        
        entry_port = request.entry_port or ""
        exit_port = request.exit_port or ""
        fecha_trans = datetime.now().strftime("%y%m%d")
        
        entry_port_desc = empresa_dict.get('entry_port_desc', 'PUERTO ENTRADA')[:15]
        exit_port_desc = empresa_dict.get('exit_port_desc', 'PUERTO SALIDA')[:15]
        main_activity = empresa_dict.get('main_activity', 'RAW MATERIAL')[:30]
        city_state = empresa_dict.get('city_state', '')[:30]

        for factura in facturas:
            self.cuenta_facturas += 1
            f_val_total = 0.0
            f_pb = 0.0
            f_pn = 0.0
            f_consec_partidas = 0 
            
            # MF01: Header
            mod_trans = factura.logistics.transport_mode if factura.logistics else "30"
            f_fecha = factura.invoice_date.strftime("%y%m%d") if factura.invoice_date else fecha_trans
            
            lineas.append(
                f"MF01{empresa_dict['broker'][:6]:<6}"
                f"{exit_port[:5]:<5}"
                f"{entry_port[:5]:<5}"
                f"{fecha_trans}"
                f"   {mod_trans[:2]:<2}"
                f"{f_fecha}"
                f"{factura.invoice_number[:15]:<15}"
            )
            self.cuenta_partidas += 1

            # MF03
            conductor = (factura.logistics.driver_name or "")[:23] if factura.logistics else ""
            carrier = (factura.logistics.carrier_id or "")[:10]
            
            lineas.append(
                f"MF03{carrier:<10}{conductor:<23}{entry_port_desc:<15}{exit_port_desc:<15}"
            )
            self.cuenta_partidas += 1

            # MF20
            num_transporte = (factura.logistics.transport_num or "")[:15] if factura.logistics else ""
            
            # Use dynamic city/state from entry_port description if available, otherwise blank
            # Clarion format: 5 digit Zip (if relevant) + City State
            # We will use the entry_port_desc (e.g. CD. JUAREZ CHIH) and assume a dummy zip '00000' if not parsed
            # Or better, just use the entry_port_desc fully aligned
            
            # Using entry_port_desc directly instead of hardcoded default
            cruce_desc = entry_port_desc[:20] if entry_port_desc else "                    "
            
            lineas.append(
                f"MF20{factura.invoice_number[:15]:<15}I{num_transporte:<15}00000{cruce_desc:<20}"
                f"{exit_port[:5]:<5}{entry_port_desc:<15}    "
            )
            self.cuenta_partidas += 1

            # MF22
            lineas.append(f"MF22{main_activity:<60}")
            self.cuenta_partidas += 1

            # IV01
            flete = int(round(float(factura.financials.freight or 0))) if factura.financials else 0
            self.flete_total += float(factura.financials.freight or 0) if factura.financials else 0.0
            
            s_tax = ""; c_tax = ""
            if factura.compliance_mx:
                if factura.compliance_mx.sold_to_id:
                    c_obj = db.query(ClientProvider).filter(ClientProvider.id == factura.compliance_mx.sold_to_id).first()
                    if c_obj: c_tax = (c_obj.rfc or "")[:12]
                if factura.compliance_mx.provider_id:
                    s_obj = db.query(ClientProvider).filter(ClientProvider.id == factura.compliance_mx.provider_id).first()
                    if s_obj: s_tax = (s_obj.rfc or "")[:12]

            # IV01 uses 11 spaces then 'C' per Clarion logic
            # Adjusted validation for RFCs to avoid crashes or None
            s_tax_safe = s_tax if s_tax else "            "
            c_tax_safe = c_tax if c_tax else "            "
            
            lineas.append(
                f"IV01{factura.invoice_number[:15]:<15}{f_fecha}78{' ':<12}C"
                f"{empresa_dict['broker'][:6]:<6}{flete:08d}{s_tax_safe:<13}{c_tax_safe:<13}"
            )
            self.cuenta_partidas += 1

            # IV02: Vendor/Manufacturer Info
            # Clarion: CliVen:ManufacterID, CliVen:Nombre
            manufacturer_id = ""
            vendor_name = ""
            if factura.compliance_mx and factura.compliance_mx.provider_id:
                 vendor = db.query(ClientProvider).options(
                     joinedload(ClientProvider.programs)
                 ).filter(ClientProvider.id == factura.compliance_mx.provider_id).first()
                 if vendor:
                      manufacturer_id = (vendor.programs.manufacturer_id or "")[:16] if vendor.programs else ""
                      vendor_name = (vendor.name or "")[:39]
            
            lineas.append(f"IV02{manufacturer_id:<16}{vendor_name:<39}")
            self.cuenta_partidas += 1

            # IV10: Responsible
            lineas.append(f"IV10    {main_activity[:30]:<30}{empresa_dict['responsable'][:30]:<30}")
            self.cuenta_partidas += 1

            # IV11: Obs
            lineas.append(f"IV11H")
            self.cuenta_partidas += 1
            
            obs_line = f"IV11F{(factura.notes or '')[:70]:<70}" if request.iv11 else "IV11F"
            lineas.append(obs_line)
            self.cuenta_partidas += 1

            # Participants IV12-14 (S, C, T, I)
            # Shipper (S)
            if factura.compliance_mx and factura.compliance_mx.provider_id:
                 s_cliente = db.query(ClientProvider).options(
                     joinedload(ClientProvider.address),
                     joinedload(ClientProvider.programs)
                 ).filter(ClientProvider.id == factura.compliance_mx.provider_id).first()
                 if s_cliente:
                     s_data = self._obtener_datos_cliente(s_cliente)
                     broker_impo = s_data['broker'] or empresa_dict['broker']
                     lineas.append(f"IV12S{broker_impo[:6]:<6}{s_data['nombre'][:39]:<39}")
                     # Ensure spacing aligns with Clarion example (30 spaces + 35 address + space + CP)
                     lineas.append(f"IV13S{'':<30}{s_data['calle']:<35} {s_data['cp']:<9}")
                     # IV14: City(20)+State(2)+Country(2)+Phone(30?? No, Clarion example shows Phone then TaxID)
                     # Clarion example: IV14S... CITY... STMX... TAXID... TEL...
                     # Re-aligning based on provided example:
                     # IV14SAKRON               NEUS                              16-0919851     00000
                     # City (20) State (2) Country (2) Space(30) TaxID(15) Tel(5?)
                     
                     lineas.append(f"IV14S{s_data['ciudad']:<20}{s_data['estado']:<2}{s_data['pais']:<2}{'':<30}{s_data['tax_id']:<15}{s_data['tel'][:5]:<5}")
                     self.cuenta_partidas += 3

            # Consignee (C), Ship To (T), and Importer (I)
            if factura.compliance_mx and factura.compliance_mx.sold_to_id:
                 c_cliente = db.query(ClientProvider).options(
                     joinedload(ClientProvider.address),
                     joinedload(ClientProvider.programs)
                 ).filter(ClientProvider.id == factura.compliance_mx.sold_to_id).first()
                 if c_cliente:
                     c_data = self._obtener_datos_cliente(c_cliente)
                     broker_impo = c_data['broker'] or empresa_dict['broker']
                     
                     # Consignee (C)
                     lineas.append(f"IV12C{broker_impo[:6]:<6}{c_data['nombre'][:39]:<39}")
                     lineas.append(f"IV13C{'':<30}{c_data['calle']:<35} {c_data['cp']:<9}")
                     lineas.append(f"IV14C{c_data['ciudad']:<20}{c_data['estado']:<2}{c_data['pais']:<2}{'':<30}{c_data['tax_id']:<15}{c_data['tel'][:5]:<5}")
                     self.cuenta_partidas += 3
                     
                     # Ship To (T) - check if different
                     t_id = factura.compliance_mx.shipped_to_id
                     if t_id and t_id != factura.compliance_mx.sold_to_id:
                          t_cl = db.query(ClientProvider).options(
                              joinedload(ClientProvider.address),
                              joinedload(ClientProvider.programs)
                          ).filter(ClientProvider.id == t_id).first()
                          if t_cl:
                               t_data = self._obtener_datos_cliente(t_cl)
                               b_t = t_data['broker'] or empresa_dict['broker']
                               lineas.append(f"IV12T{b_t[:6]:<6}{t_data['nombre'][:39]:<39}")
                               lineas.append(f"IV13T{'':<30}{t_data['calle']:<35} {t_data['cp']:<9}")
                               lineas.append(f"IV14T{t_data['ciudad']:<20}{t_data['estado']:<2}{t_data['pais']:<2}{'':<30}{t_data['tax_id']:<15}{t_data['tel'][:5]:<5}")
                               self.cuenta_partidas += 3

                     # Importer (I) - Usually same as Consignee in Definitive unless ShippedBy is set
                     i_id = factura.compliance_mx.shipped_by_id
                     if i_id and i_id != factura.compliance_mx.sold_to_id and i_id != t_id:
                          i_cl = db.query(ClientProvider).options(
                              joinedload(ClientProvider.address),
                              joinedload(ClientProvider.programs)
                          ).filter(ClientProvider.id == i_id).first()
                          if i_cl:
                               i_data = self._obtener_datos_cliente(i_cl)
                               b_i = i_data['broker'] or empresa_dict['broker']
                               lineas.append(f"IV12I{b_i[:6]:<6}{i_data['nombre'][:39]:<39}")
                               lineas.append(f"IV13I{'':<30}{i_data['calle']:<35} {i_data['cp']:<9}")
                               lineas.append(f"IV14I{i_data['ciudad']:<20}{i_data['estado']:<2}{i_data['pais']:<2}{'':<30}{i_data['tax_id']:<15}{i_data['tel'][:5]:<5}")
                               self.cuenta_partidas += 3
                     else:
                          # Fallback: repeat C as I if not specified (following Clarion pattern)
                          lineas.append(f"IV12I{broker_impo[:6]:<6}{c_data['nombre'][:39]:<39}")
                          lineas.append(f"IV13I{'':<30}{c_data['calle']:<35} {c_data['cp']:<9}")
                          lineas.append(f"IV14I{c_data['ciudad']:<20}{c_data['estado']:<2}{c_data['pais']:<2}{'':<30}{c_data['tax_id']:<15}{c_data['tel'][:5]:<5}")
                          self.cuenta_partidas += 3

            # Items IV20-27
            items_headers = db.query(LineItem).filter(LineItem.invoice_id == factura.id).all()
            item_ids = [ih.id for ih in items_headers]
            
            if item_ids:
                items_query = db.query(LineItem).filter(
                    LineItem.id.in_(item_ids)
                ).options(
                    joinedload(LineItem.part_info),
                    joinedload(LineItem.description),
                    joinedload(LineItem.financial),
                    joinedload(LineItem.quantity),
                    joinedload(LineItem.customs),
                    joinedload(LineItem.unit_of_measure_info)
                ).all()

                for line in items_query:
                    f_consec_partidas += 1
                    part_num = (line.part_info.part_number if line.part_info else "S/N")[:25]
                    po = (line.order or "")[:20] if line.financial else ""
                    
                    # IV20
                    lineas.append(f"IV20{f_consec_partidas:03d}  {part_num:<25}C {po:<20}")
                    self.cuenta_partidas += 1

                    # IV21
                    desc_ingles = (line.description.description_english or "")[:50] if line.description else ""
                    lineas.append(f"IV21                    {desc_ingles:<50}")
                    self.cuenta_partidas += 1

                    # Data for IV22
                    val_me = float(line.financial.value_usd or 0) if line.financial else 0.0
                    qty = float(line.quantity.quantity or 0) if line.quantity else 0.0
                    pb = float(line.quantity.gross_weight or 0) if line.quantity else 0.0
                    pn = float(line.quantity.net_weight or 0) if line.quantity else 0.0
                    costo_u = float(line.financial.unit_cost_capture or 0) if line.financial else 0.0
                    
                    um = (line.unit_of_measure_info.american_code or "PCS")[:3] if line.unit_of_measure_info else "PCS"
                    pais = ((line.customs.origin_country or "MX")[:2]).upper() if line.customs else "MX"
                    hts = (line.customs.american_fraction or "").replace(".", "")[:10] if line.customs else ""

                    # Formato Clarion @n...v...
                    val_int = int(round(val_me * 100)) # @n010v2
                    qty_int = int(round(qty * 100)) # @n09v2
                    pb_int = int(round(pb * 100)) # @n09v2
                    pn_int = int(round(pn * 100)) # @n09v2
                    costo_int = int(round(costo_u * 100000)) # @n011v5
                    
                    prog_ind = "S" if line.customs and line.customs.has_origin_certificate else "N"

                    # IV22
                    lineas.append(
                        f"IV22{hts:<10}{prog_ind}"
                        f"{val_int:010d}{um:<3}{qty_int:09d}{pais:<2}0000010000000000100000 "
                        f"{pb_int:09d}{pn_int:09d}"
                    )
                    self.cuenta_partidas += 1

                    # IV24, IV26 (Zeros per logic)
                    lineas.append(f"IV24           0000000000   000000000  0000000000000000000000")
                    lineas.append(f"IV26           0000000000   000000000  0000000000000000000000")
                    self.cuenta_partidas += 2

                    # IV27
                    lineas.append(f"IV27{hts:<10}{costo_int:011d}0000000000000000000000000000000000000            ")
                    self.cuenta_partidas += 1

                    # IV42 (Optional)
                    if request.iv42:
                         um4 = um[:4]
                         lineas.append(f"IV42  0{qty_int:09d}{um4:<4}0000000000    0000000000    0000000000    0000000000    ")
                         self.cuenta_partidas += 1

                    f_val_total += val_me
                    f_pb += pb
                    f_pn += pn

            # IV90: Footer per Invoice
            fv_int = int(round(f_val_total * 100))
            fpb_int = int(round(f_pb * 100))
            fpn_int = int(round(f_pn * 100))
            lineas.append(f"IV90{f_consec_partidas:05d}{fv_int:012d}{fpb_int:010d}{fpn_int:010d}")
            self.cuenta_partidas += 1

            self.valor_total_factura += f_val_total
            self.peso_bruto_factura += f_pb
            self.peso_neto_factura += f_pn

        return lineas, self.errores
