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

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.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"""
        # Determine Tax ID: RFC for MX, Tax ID for others
        address = cliente.address
        pais_raw = (address.country or "MX").upper()
        
        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 = ""
        if pais == "MX":
            tax_id = cliente.rfc or ""
        else:
            # Try generic tax_id field if exists, else generic field or RFC as fallback
            # Providing a fallback to extra_information or web_key if needed, but per model inspection:
            # We don't see a specific 'tax_id' field in ClientProvider model snippet. 
            # We see 'rfc'. Let's use RFC as generic holder or look for 'tax_id' if I missed it.
            # Re-reading model: rfc is the only obvious one. 
            # Let's use RFC field for foreign tax id too unless instructed otherwise.
            tax_id = cliente.rfc or ""

        data = {
            "nombre": (cliente.name or "")[:39],
            "tax_id": tax_id[:15],
            "broker": "", "calle": "", "cp": "", "ciudad": "", "estado": "", "pais": pais, "tel": ""
        }
        
        if cliente.programs:
            data["broker"] = (cliente.programs.broker or "")[:6]
            
        if address:
            calle_comp = f"{address.streets or ''} {address.exterior_number or ''}".strip()
            data["calle"] = calle_comp[:35]
            data["cp"] = (address.postal_code or "")[:9]
            data["ciudad"] = (address.city or "")[:20]
            data["estado"] = (address.state or "")[:2].upper()
            data["tel"] = (address.phone or "")[:15] # Remove default "000000"
            
        return data

    def procesar_facturas(
        self, db: Session, manifiesto: str, empresa_dict: Dict[str, Any], request: Mainx30GenerationRequest
    ) -> Tuple[List[str], List[ErrorValidacion]]:
        lineas = []
        self.errores = []
        
        # 1. Traer Facturas del Manifiesto
        facturas = db.query(InvoiceHeader).join(
            InvoiceComplianceMx, InvoiceHeader.id == InvoiceComplianceMx.invoice_id
        ).options(
            joinedload(InvoiceHeader.financials),
            joinedload(InvoiceHeader.compliance_mx)
        ).filter(
            InvoiceComplianceMx.manifest_number == manifiesto
        ).all()
        
        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 

            # --- MF20 / MF22: Per-Invoice Header at Manifest Level ---
            # Sample: MF20AAK22-001      I10900          1234                1234
            # Invoice(15) + Type(1?) + Port(5?) + ...
            entry_port = manifiesto.replace("-", "")[:4] # or from manifest object if available here? 
            # Manifiesto passed to this method is just a string 'manifest_number'. 
            # We need to query manifest or pass it. 
            # Actually, `manifiesto` arg is just the number string.
            # But we can pass the entry_port from service.py in empresa_dict or request? 
            # Let's check service.py.
            
            # Assuming it is in empresa_dict for now (I will add it next step)
            # --- MF20 / MF22: Per-Invoice Header at Manifest Level ---
            # Sample: MF20AAK22-001      I10900          1234                1234
            
            port_code = empresa_dict.get('entry_port', '')[:4]
            manufacturer_id = empresa_dict.get('manufacturer_id', '')[:10]
            
            # Constructing line to match sample length/spacing
            lineas.append(
                f"MF20"
                f"{factura.invoice_number[:15]:<15}"
                f"I{manufacturer_id:<15}" 
                f"{port_code:<20}" 
                f"{port_code:<4}"  
            )
            lineas.append(f"MF22")
            self.cuenta_partidas += 2

            # --- IV01: Header de Factura ---
            flete = float(factura.financials.freight) if factura.financials and factura.financials.freight else 0.0
            fecha_str = factura.invoice_date.strftime("%y%m%d") if factura.invoice_date else "000000"
            
            s_rfc = ""
            c_rfc = ""
            
            if factura.compliance_mx:
                if factura.compliance_mx.provider_id:
                     s_obj = db.query(ClientProvider).filter(ClientProvider.id == factura.compliance_mx.provider_id).first()
                     if s_obj: s_rfc = s_obj.rfc or ""
                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_rfc = c_obj.rfc or ""

            lineas.append(
                f"IV01{factura.invoice_number[:15]:<15}"
                f"{fecha_str}01 " # 6 + 3 = 9
                f"{port_code:<11}" # Port (Use same as MF20)
                f"{empresa_dict.get('broker', '')[:6]:<15}" # Broker
                f"{s_rfc[:12]:<12}{c_rfc[:12]:<12}" 
            )
            self.cuenta_partidas += 1

            # --- IV02: Company Name ---
            nombre_empresa = empresa_dict.get('nombre_empresa', '')[:40]
            lineas.append(f"IV02                {nombre_empresa:<40}")
            self.cuenta_partidas += 1

            # --- IV10: Goods Description & Contact ---
            # Dynamic Description from Invoice (observation_en or observation_es)
            desc_global = (factura.observation_en or factura.observation_es or "")[:30]
            
            contacto = empresa_dict.get('responsable', '')[:30]
            lineas.append(f"IV10    {desc_global:<30}{contacto:<30}")
            self.cuenta_partidas += 1

            # --- IV11: Headers ---
            lineas.append(f"IV11H")
            lineas.append(f"IV11F")
            self.cuenta_partidas += 2

            # --- DATOS DE DIRECCIONES (S, C, T, I) ---
            # Shipper (S) -> Proveedor de la factura
            if factura.compliance_mx and factura.compliance_mx.provider_id:
                s_cliente = db.query(ClientProvider).filter(ClientProvider.id == factura.compliance_mx.provider_id).first()
                if s_cliente:
                    s_data = self._obtener_datos_cliente(s_cliente)
                    calle_cp = f"{s_data['calle']} {s_data['cp']}".strip()
                    lineas.append(f"IV12S      {s_data['nombre'][:39]:<39}")
                    lineas.append(f"IV13S                              {calle_cp[:35]:<35}")
                    lineas.append(f"IV14S{s_data['ciudad'][:20]:<20}{s_data['estado'][:2]}{s_data['pais'][:2]}{s_data['tel'][:15]:<15}{s_data['tax_id']:<15}00000")
                    self.cuenta_partidas += 3

            # Consignee / Vendido A (C)
            c_data = None
            if factura.compliance_mx and factura.compliance_mx.sold_to_id:
                c_cliente = db.query(ClientProvider).filter(ClientProvider.id == factura.compliance_mx.sold_to_id).first()
                if c_cliente:
                    c_data = self._obtener_datos_cliente(c_cliente)
                    calle_cp = f"{c_data['calle']} {c_data['cp']}".strip()
                    lineas.append(f"IV12C      {c_data['nombre'][:39]:<39}")
                    lineas.append(f"IV13C                              {calle_cp[:35]:<35}")
                    lineas.append(f"IV14C{c_data['ciudad'][:20]:<20}{c_data['estado'][:2]}{c_data['pais'][:2]}{c_data['tel'][:15]:<15}{c_data['tax_id']:<15}00000")
                    self.cuenta_partidas += 3

            # Ship To / Enviado A (T)
            if factura.compliance_mx and factura.compliance_mx.shipped_to_id:
                t_cliente = db.query(ClientProvider).filter(ClientProvider.id == factura.compliance_mx.shipped_to_id).first()
                if t_cliente:
                    t_data = self._obtener_datos_cliente(t_cliente)
                    calle_cp = f"{t_data['calle']} {t_data['cp']}".strip()
                    lineas.append(f"IV12T      {t_data['nombre'][:39]:<39}")
                    lineas.append(f"IV13T                              {calle_cp[:35]:<35}")
                    lineas.append(f"IV14T{t_data['ciudad'][:20]:<20}{t_data['estado'][:2]}{t_data['pais'][:2]}{t_data['tel'][:15]:<15}{t_data['tax_id']:<15}00000")
                    self.cuenta_partidas += 3

            # Importer (I) - Sample shows it same as Consignee or Importer
            if c_data:
                # Reuse c_data calculation or re-fetch if needed. Reusing c_data structure.
                calle_cp = f"{c_data['calle']} {c_data['cp']}".strip()
                lineas.append(f"IV12I      {c_data['nombre'][:39]:<39}")
                lineas.append(f"IV13I                              {calle_cp[:35]:<35}")
                lineas.append(f"IV14I{c_data['ciudad'][:20]:<20}{c_data['estado'][:2]}{c_data['pais'][:2]}{c_data['tel'][:15]:<15}{c_data['tax_id']:<15}00000")
                self.cuenta_partidas += 3

            # --- PARTIDAS (DETALLE IV20-IV27) ---
            items_query = db.query(LineItem).filter(
                LineItem.invoice_id == factura.id
            ).options(
                joinedload(LineItem.part_info),
                joinedload(LineItem.description),
                joinedload(LineItem.financial),
                joinedload(LineItem.quantity),           # Added quantity relation
                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"
                po_num = factura.purchase_order or ""
                
                desc = ""
                if line.description:
                    desc = line.description.description_english or line.description.description_spanish or ""
                
                # --- OBTENCIÓN DE DATOS DE LINEFINANCIAL / LINEQUANTITY ---
                qty = 0.0; pb = 0.0; pn = 0.0; val_usd = 0.0
                val_no_duty = 0.0; val_packing = 0.0
                
                if line.quantity:
                    qty = float(line.quantity.quantity or 0.0)
                    pb = float(line.quantity.gross_weight or 0.0)
                    pn = float(line.quantity.net_weight or 0.0)
                    
                    if pb == 0 and pn > 0: pb = pn

                if line.financial:
                    val_usd = float(line.financial.value_usd or 0.0)
                    val_no_duty = float(line.financial.exempt_amount_usd or 0.0) # IV24
                    val_packing = float(line.financial.value_us_packing_usd or 0.0) # IV26
                
                f_pb += pb
                f_pn += pn
                f_val_total += val_usd # Assuming Total Invoice Value is sum of line.value_usd

                # Aduanas
                hts_ame = ""
                pais_orig = "MX"
                if line.customs:
                    raw_hts = line.customs.american_fraction or line.customs.fraction or ""
                    hts_ame = raw_hts.replace(".", "").strip()
                    pais_orig = (line.customs.origin_country or "MX")[:2]

                # UM
                um_ame = "PC"
                if line.unit_of_measure_info:
                    um_ame = line.unit_of_measure_info.american_code or "PC"

                # Escritura (Igual que el Clarion)
                lineas.append(f"IV20{f_consec_partidas:03d}  {part_num[:25]:<25}A {po_num[:20]:<20}")
                lineas.append(f"IV21{' ':21}{desc[:50]:<50}")
                
                # IV22: Fix alignment based on sample
                # Sample:           N0000002235PCS000050000CN0000010000000000000000 000000549000000408
                # HTS(10?) + Val(10) + UM(3) + Cant(9) + Pais(2) + ...
                
                v_int = int(round(val_usd * 100))
                q_int = int(round(qty * 1000)) # Sample 000050000 for 50? 50 * 1000 = 50000. 
                pb_int = int(round(pb * 100))
                pn_int = int(round(pn * 100))

                lineas.append(
                    f"IV22          N"               # 10 spaces + N
                    f"{v_int:010d}"                 # Value (integer 10)
                    f"{um_ame[:3]:<3}"              # UM (3)
                    f"{q_int:09d}"                  # Qty (integer 9)
                    f"{pais_orig[:2]:<2}"           # Pais (2)
                    f"0000010000000000000000 "      # Fixed (23 with space)
                    f"{pb_int:010d}"                # Peso Bruto (10 chars)
                    f"{pn_int:010d}"                 # Peso Neto (10 chars)
                )

                # IV24 (No Duty / Exempt)
                # Dynamic Logic: Use exempt_amount_usd if > 0
                v_nd_int = int(round(val_no_duty * 100))
                # IV24 uses same UM and Qty layout as IV22 but for NoDuty portion?
                # Sample shows just value and then mostly zeros?
                # Sample: IV24           0000000000   000000000  0000000000000000000000
                # We will use v_nd_int. If 0, it renders as 0000000000.
                if v_nd_int > 0:
                     # If there IS a No Duty value, we should probably output it.
                     # Format seems to start at same pos as IV22 Value?
                     # IV22 starts value at col 20 (approx).
                     # IV24 starts value at col 20 (approx). 
                     # IV24           {Val}   {Qty?} ...
                     # Given sample: `IV24           0000000000   000000000  ...`
                     # It looks like: Prefix(15) + Val(10) + Space(3) + Qty??(9) + ...
                     # Let's mimic structure 
                     lineas.append(f"IV24           {v_nd_int:010d}   {0:09d}  0000000000000000000000")
                else:
                     lineas.append(f"IV24           {0:010d}   {0:09d}  0000000000000000000000")

                # IV26 (Packing)
                # Dynamic Logic: Use value_us_packing_usd
                v_p_int = int(round(val_packing * 100))
                if v_p_int > 0:
                     lineas.append(f"IV26           {v_p_int:010d}   {0:09d}  0000000000000000000000")
                else:
                     lineas.append(f"IV26           {0:010d}   {0:09d}  0000000000000000000000")
                
                # IV27 (Unit Costs)
                # Sample: IV27          000000000000000000000000000000000000000000000000000000000000000000
                # If we have distinct values, maybe we should calculate unit costs?
                # But legacy sample shows all zeros. 
                # Calculating separate unit costs for Duty/NoDuty/Packing:
                c_u_d = val_usd / qty if qty > 0 else 0
                c_u_nd = val_no_duty / qty if qty > 0 else 0
                c_u_p = val_packing / qty if qty > 0 else 0
                
                # If user wants NO HARDCODING, maybe we should populate this?
                # But sample had 0s. Let's populate specific costs if values exist, else 0.
                # Format: IV27 + 10 spaces + CostDuty(11) + CostNoDuty(11) + CostPacking(11) + ...
                # Based on legacy Clarion: `FORMAT(Left(Loc:CostoUDuty),@n011v5)`
                
                cud_int = int(round(c_u_d * 100000))
                cund_int = int(round(c_u_nd * 100000))
                cup_int = int(round(c_u_p * 100000))
                
                lineas.append(f"IV27          {cud_int:011d}{cund_int:011d}{cup_int:011d}000000000000000000000000000000000")
                
                self.cuenta_partidas += 6

            # --- TOTALES FACTURA ---
            # Sample: IV900000700000000000000000063320000005348
            # IV90 + CantPartidas(5) + ValTotal(12) + PesoBruto(10) + PesoNeto(10)
            f_val_int = int(round(f_val_total * 100))
            f_pb_int = int(round(f_pb * 100))
            f_pn_int = int(round(f_pn * 100))
            lineas.append(f"IV90{f_consec_partidas:05d}{f_val_int:012d}{f_pb_int:010d}{f_pn_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

    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))

# (Dummy Processors para que no truene el Service)
class ScafDefProcessor:
    def __init__(self): self.cuenta_partidas=0; self.cuenta_facturas=0; self.valor_total_factura=0; self.peso_bruto_factura=0; self.peso_neto_factura=0
    def procesar_facturas(self, db, manifiesto, empresa_dict, request): return [], []

class ScafTempProcessor:
    def __init__(self): self.cuenta_partidas=0; self.cuenta_facturas=0; self.valor_total_factura=0; self.peso_bruto_factura=0; self.peso_neto_factura=0
    def procesar_facturas(self, db, manifiesto, empresa_dict, request): return [], []