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": ""
        }
        
        if cliente.programs:
            data["broker"] = (cliente.programs.broker or "")[:6]
            
        if address:
            calle_comp = f"{address.streets or ''} {address.exterior_number or ''}".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 "")[:20]
            data["estado"] = (address.state or "")[:2].upper()
            data["tel"] = (address.phone or "")[:15]
            
        return data

    def procesar_facturas(
        self, db: Session, manifiesto: str, empresa_dict: Dict[str, Any], request: Mainx30GenerationRequest
    ) -> Tuple[List[str], List[ErrorValidacion]]:
        # This base method is used for Manifest-based processing (Exportacion/Legacy)
        lineas = []
        self.errores = []
        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))

# --- PROCESADORES ESPECIFICOS ---
class ScafDefProcessor(ScaiiProcessor):
    """Procesador para Importación Definitiva"""
    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]]:
        # Placeholder for Definitiva logic
        lineas = []
        self.errores = []
        return lineas, self.errores

class ScafTempProcessor(ScaiiProcessor):
    """Procesador para Importación Temporal basado en lógica Clarion"""
    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 (Importación Temporal trabaja por factura)
        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()
        
        # Helper for ports from request
        entry_port = request.entry_port or ""
        exit_port = request.exit_port or ""
        
        # Date for transmission records (YYMMDD)
        fecha_trans = datetime.now().strftime("%y%m%d")
        
        # Port descriptions from empresa_dict (populated in service.py)
        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 per Invoice in Importacion Temporal
            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: Dynamic Driver/Carrier info
            conductor = (factura.logistics.driver_name or "") if factura.logistics else ""
            
            # Carrier Logic from Clarion:
            # IF ERRORCODE() = 35 THEN Loc:NumTransporte = '00000TRUCK'
            # ELSE IF GenTra:NombreCorto = '' THEN Loc:NumTransporte = GenTra:Nombre
            # ELSE Loc:NumTransporte = GenTra:NombreCorto
            carrier = "00000TRUCK"
            if factura.logistics:
                 # Logic simplified: assume carrier_id holds the correct code/name or fallback
                 carrier = (factura.logistics.carrier_id or "00000TRUCK")
            
            lineas.append(
                f"MF03{carrier[:10]:<10}{conductor[:23]:<23}{entry_port_desc:<15}{exit_port_desc:<15}"
            )
            self.cuenta_partidas += 1

            # MF20
            num_transporte = (factura.logistics.transport_num or "") if factura.logistics else ""
            lineas.append(
                f"MF20{factura.invoice_number[:15]:<15}I{num_transporte[:15]:<15}{city_state[:20]:<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: Header
            f_fecha = factura.invoice_date.strftime("%y%m%d") if factura.invoice_date else "000000"
            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[:12] if c_obj.rfc else ""
                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[:12] if s_obj.rfc else ""

            lineas.append(
                f"IV01{factura.invoice_number[:15]:<15}{f_fecha}{entry_port:<5}{' ':<11}C"
                f"{empresa_dict['broker'][:6]:<6}{flete:08d}{c_tax:<13}"
            )
            self.cuenta_partidas += 1

            # IV02: Shipper Name
            lineas.append(f"IV02                {empresa_dict['nombre_empresa'][:40]:<40}")
            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")
            lineas.append(f"IV11F")
            self.cuenta_partidas += 2

            # IV12-14 (S, C, T, I)
            # Shipper (S)
            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)
                     lineas.append(f"IV12S      {s_data['nombre'][:39]:<39}")
                     lineas.append(f"IV13S                              {s_data['calle'][:35]:<35}{s_data['cp']:<9}")
                     lineas.append(f"IV14S{s_data['ciudad'][:20]:<20}{s_data['state_full'][:2] if 'state_full' in s_data else s_data['estado'][:2]}{s_data['pais'][:2]}{s_data['tel'][:30]:<30}{s_data['tax_id']:<15}00000")
                     self.cuenta_partidas += 3

            # Consignee (C), Ship To (T), and Intermediate (I)
            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)
                     l12c = f"IV12C      {c_data['nombre'][:39]:<39}"
                     l13c = f"IV13C                              {c_data['calle'][:35]:<35}{c_data['cp']:<9}"
                     l14c = f"IV14C{c_data['ciudad'][:20]:<20}{c_data['estado'][:2]}{c_data['pais'][:2]}{c_data['tel'][:30]:<30}{c_data['tax_id']:<15}00000"
                     
                     # Output C
                     lineas.extend([l12c, l13c, l14c])
                     self.cuenta_partidas += 3
                     
                     # T (Ship To) - Only if different from C
                     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).filter(ClientProvider.id == t_id).first()
                          if t_cl:
                               t_data = self._obtener_datos_cliente(t_cl)
                               lineas.append(f"IV12T      {t_data['nombre'][:39]:<39}")
                               lineas.append(f"IV13T                              {t_data['calle'][:35]:<35}{t_data['cp']:<9}")
                               lineas.append(f"IV14T{t_data['ciudad'][:20]:<20}{t_data['estado'][:2]}{t_data['pais'][:2]}{t_data['tel'][:30]:<30}{t_data['tax_id']:<15}00000")
                               self.cuenta_partidas += 3

                     # I (Intermediate) - Only if different from C and T
                     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).filter(ClientProvider.id == i_id).first()
                          if i_cl:
                               i_data = self._obtener_datos_cliente(i_cl)
                               lineas.append(f"IV12I      {i_data['nombre'][:39]:<39}")
                               lineas.append(f"IV13I                              {i_data['calle'][:35]:<35}{i_data['cp']:<9}")
                               lineas.append(f"IV14I{i_data['ciudad'][:20]:<20}{i_data['estado'][:2]}{i_data['pais'][:2]}{i_data['tel'][:30]:<30}{i_data['tax_id']:<15}00000")
                               self.cuenta_partidas += 3

            # Partidas IV20, IV21, IV22, IV24, IV26, IV27
            # Let's get Item IDs first to ensure we find them
            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"
                    
                    # IV20
                    lineas.append(f"IV20{f_consec_partidas:03d}  {part_num[:25]:<25}C")
                    self.cuenta_partidas += 1

                    # IV21
                    lineas.append(f"IV21")
                    self.cuenta_partidas += 1

                    # Data
                    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 if line.unit_of_measure_info else "PCS"
                    pais = (line.customs.origin_country or "MX")[:2] if line.customs else "MX"
                    hts = (line.customs.american_fraction or "").replace(".", "")[:10] if line.customs else ""

                    val_int = int(round(val_me * 10000))
                    qty_int = int(round(qty * 10000))
                    pb_int = int(round(pb * 10000))
                    pn_int = int(round(pn * 10000))
                    costo_int = int(round(costo_u * 100000)) 

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

                    # IV24, IV26 (Zeros)
                    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

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

            # IV90: Footer per Invoice

            # IV90
            f_val_int = int(round(f_val_total * 100))
            f_pb_int = int(round(f_pb * 10000))
            f_pn_int = int(round(f_pn * 10000))
            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

class ScaiiTempProcessor(ScafTempProcessor):
    """
    Procesador para Importación Temporal (Versión Ligera/SCAII)
    Se salta los registros MF20, MF22, IV10-14 para mayor velocidad y menor detalle.
    """
    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]

        for factura in facturas:
            self.cuenta_facturas += 1
            f_val_total = 0.0
            f_consec_partidas = 0 

            # MF01
            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}{exit_port[:5]:<5}{entry_port[:5]:<5}"
                f"{fecha_trans}   {mod_trans[:2]:<2}{f_fecha}{factura.invoice_number[:15]:<15}"
            )
            self.cuenta_partidas += 1

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

            # IV01
            f_fecha_iv = factura.invoice_date.strftime("%y%m%d") if factura.invoice_date else "000000"
            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
            
            lineas.append(
                f"IV01{factura.invoice_number[:15]:<15}{f_fecha_iv}{entry_port:<5}{' ':<11}C"
                f"{empresa_dict['broker'][:6]:<6}{flete:08d}{'':<13}"
            )
            self.cuenta_partidas += 1

            # Partidas IV20, IV21, IV22
            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)).all()

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

                    # IV22
                    qty = float(line.quantity.quantity or 0) if line.quantity else 0.0
                    val_me = float(line.financial.value_usd or 0) if line.financial else 0.0
                    hts = (line.customs.american_fraction or "").replace(".", "")[:10] if line.customs else ""
                    
                    val_int = int(round(val_me * 10000))
                    qty_int = int(round(qty * 10000))

                    lineas.append(f"IV22{hts:<10} {val_int:010d}PCS{qty_int:09d}MX0000010000000000100000 00000000000000000000")
                    self.cuenta_partidas += 1
                    f_val_total += val_me

            # IV90
            fv_int = int(round(f_val_total * 100))
            lineas.append(f"IV90{f_consec_partidas:05d}{fv_int:012d}00000000000000000000")
            self.cuenta_partidas += 1

            self.valor_total_factura += f_val_total

        return lineas, self.errores