import os
import tempfile
from datetime import date, datetime
from typing import List, Tuple, Optional
from pathlib import Path

from sqlalchemy.orm import Session
from fastapi import HTTPException

from .schemas import (
    Mainx30GenerationRequest, Mainx30Response, ErrorValidacion,
    EmpresaDatos, ConfiguracionSistema
)

# --- HELPERS ---
def fecha_clarion_a_iso(clarion_date):
    """Convierte fecha Clarion (días desde 1800-12-28) a ISO YYYY-MM-DD"""
    if not clarion_date: return "1900-01-01"
    try:
        from datetime import date, timedelta
        base_date = date(1800, 12, 28)
        delta = timedelta(days=int(clarion_date))
        return (base_date + delta).isoformat()
    except:
        return "1900-01-01"

# --- MODELOS A76 ---
from api.v1.modules.a76.manifests.manifest.models import Manifest
from api.v1.modules.a76.general_catalogs.company.models import Company as GEmpresa

# --- PROCESADORES ---
from .processors import ScaiiProcessor, ScafDefProcessor, ScafTempProcessor

class Mainx30Service:
    def __init__(self):
        self.errores_validacion: List[ErrorValidacion] = []
        self.cuenta_partidas = 0
        self.cuenta_facturas = 0
        self.valor_total = 0.0
        self.flete_total = 0.0
        self.peso_bruto_total = 0.0
        self.peso_neto_total = 0.0
        
    def generar_mainx30_expo(
        self, 
        db: Session, 
        request: Mainx30GenerationRequest,
        task_instance=None
    ) -> Mainx30Response:
        try:
            self._inicializar_variables()
            fecha_transmision = date.today().strftime("%y%m%d")
            
            config_sistema = self._obtener_configuracion_sistema(db)
            datos_empresa = self._obtener_datos_empresa(db)
            self._validar_datos_empresa(datos_empresa)
            
            if not request.manifiestos:
                raise HTTPException(status_code=400, detail="No se seleccionaron manifiestos")

            nombre_archivo = self._generar_nombre_archivo(config_sistema, request, request.manifiestos[0])
            lineas_archivo = []
            
            # Línea A
            lineas_archivo.append(self._generar_linea_a(fecha_transmision, datos_empresa))
            
            for manifiesto_num in request.manifiestos:
                if task_instance:
                    task_instance.update_state(state='PROCESSING', meta={'status': f'Procesando {manifiesto_num}'})

                lineas_manifiesto = self._procesar_manifiesto(
                    db, manifiesto_num, datos_empresa, fecha_transmision, request
                )
                lineas_archivo.extend(lineas_manifiesto)
            
            # Línea Z
            lineas_archivo.append(f"Z {self.cuenta_partidas:05d}")
            
            ruta_completa = os.path.join("api/v1/modules/reports/generated", nombre_archivo)
            self._escribir_archivo(ruta_completa, lineas_archivo)
            
            return Mainx30Response(
                success=len(self.errores_validacion) == 0,
                message=self._generar_mensaje_resultado(ruta_completa),
                archivo_generado=nombre_archivo,
                ruta_archivo=ruta_completa,
                cuenta_partidas=self.cuenta_partidas,
                valor_total=self.valor_total,
                flete_total=self.flete_total,
                peso_bruto_total=self.peso_bruto_total,
                peso_neto_total=self.peso_neto_total,
                cuenta_facturas=self.cuenta_facturas,
                errores=self.errores_validacion,
                tiene_inconsistencias=len(self.errores_validacion) > 0,
                content="\r\n".join(lineas_archivo)
            )
            
        except Exception as e:
            import traceback
            traceback.print_exc()
            raise HTTPException(status_code=500, detail=f"Error generando Mainx30: {str(e)}")
    
    def _inicializar_variables(self):
        self.errores_validacion = []
        self.cuenta_partidas = 1 # Empieza en 1 por la línea A
        self.valor_total = 0.0
        self.flete_total = 0.0
        self.peso_bruto_total = 0.0
        self.peso_neto_total = 0.0
        self.cuenta_facturas = 0
    
    def _procesar_manifiesto(
        self, 
        db: Session, 
        manifiesto_num: str, 
        datos_empresa: EmpresaDatos,
        fecha_transmision: str,
        request: Mainx30GenerationRequest
    ) -> List[str]:
        lineas = []
        
        # --- TABLA A76: MANIFEST ---
        manifiesto = db.query(Manifest).filter(
            Manifest.manifest_number == manifiesto_num
        ).first()
        
        if not manifiesto:
            self._agregar_error_validacion(0, "MF", f"Manifiesto {manifiesto_num} no encontrado.", "Verificar BD", "MANIFIESTO")
            return lineas

        persona_cargo = manifiesto.person_in_charge or ""
        if not persona_cargo:
            self._agregar_error_validacion(0, "MF03", "Falta Persona a Cargo", "Capturar en Manifiesto", "MANIFIESTO")

        num_manifiesto_clean = manifiesto_num.replace("-", "")
        
        # Fecha en formato yyMMdd. Asumimos entry_date almacena Clarion Date o Timestamp.
        fecha_entrada_str = "000000"
        if manifiesto.entry_date:
            try:
                # Si es Clarion Date
                fecha_iso = fecha_clarion_a_iso(manifiesto.entry_date)
                fecha_entrada_str = datetime.strptime(fecha_iso, "%Y-%m-%d").strftime("%y%m%d")
            except: pass

        firms_code = manifiesto.entry_port_loc or ""
        entry_port = manifiesto.entry_port or "000"

        # MF01
        # Sample Clarion: MF01AKR   1234 1234 2602061233026021345
        # Layout:
        # MF01 (4)
        # Broker (6) -> "AKR   "
        # Port Ent (5) -> "1234 "
        # Port Sal (5) -> "1234 "
        # FecEnt (6) -> "260206"
        # 12 (2) -> Prefix?
        # 3 (1) -> Digit 3?
        # 30 (2) -> Constant?
        # FecTrans (6) -> "260213"
        # Manifiesto (15?) -> "45             " (Sample has '45' at end, maybe manifest is '45'?)
        
        # Let's align with sample string length and fields.
        # "MF01"
        # Broker: Left aligned 6 chars
        # Port1: Left aligned 5 chars
        # Port2: Left aligned 5 chars
        # Date1: 6 chars
        # "12330" (Hardcoded sequence based on sample analysis vs previous logic)
        # Date2: 6 chars
        # Manifest: Left aligned 15 chars? Sample "45" is at end.
        
        # Re-analyzing sample: "MF01AKR   1234 1234 2602061233026021345"
        # Length: 4+6+5+5+6+2+1+2+6+2 = 39? No.
        # AKR   : 6
        # 1234 : 5
        # 1234 : 5
        # 260206: 6
        # 12: 2
        # 3: 1
        # 30: 2
        # 260213: 6
        # 45: 2?
        # Total: 4+6+5+5+6+5+6+2 = 39 chars displayed.
        
        # My generated was: MF01123   000  000  0001011230260213123456879 
        # It was way off.
        
        man_clean = num_manifiesto_clean[:15]
        
        lineas.append(
            f"MF01{datos_empresa.broker:<6}"
            f"{entry_port:<5}"
            f"{entry_port:<5}"
            f"{fecha_entrada_str}"
            f"12330{fecha_transmision}" # Fixed sequence "12330" inferred from sample
            f"{man_clean:<15}" 
        )
        self.cuenta_partidas += 1
        
        # MF03
        # Sample: MF03FRANCISCO 1234
        # MF03 (4)
        # Person (Top Left?) 
        # Sample: "MF03FRANCISCO 1234"
        # It seems "FRANCISCO" is right after MF03. That's the PERSON.
        # "1234" is the Gafete/License.
        # My previous code put Carrier first: "MF03TRUCK     Lopez Doriga..."
        # Correct mapping: MF03 + Person(Included Name) + License
        
        # Let's follow sample:
        # MF03 + Person(15?) + License(15?)
        # MF03
        transportista = manifiesto.carrier_code or "" 
        persona = persona_cargo or ""
        # 'driver_license' attribute does not exist in Manifest model. 
        # Using 'transport_code' or similar as fallback for license/gafete.
        licencia = manifiesto.transport_code or ""
        
        lineas.append(
           f"MF03{persona[:15]:<15} {licencia[:15]:<15}"
        )
        self.cuenta_partidas += 1
        
        # --- PROCESAR FACTURAS ---
        empresa_dict = {
            'broker': datos_empresa.broker,
            'responsable': datos_empresa.responsable,
            'rfc': datos_empresa.rfc,
            'nombre_empresa': datos_empresa.nombre_empresa,
            'entry_port': entry_port,
            'manufacturer_id': datos_empresa.manufacturer_id
        }
        
        processor = ScaiiProcessor()
        l_facturas, e_facturas = processor.procesar_facturas(db, manifiesto_num, empresa_dict, request)
        
        lineas.extend(l_facturas)
        self.errores_validacion.extend(e_facturas)
        
        # Actualizar acumuladores Globales
        self.cuenta_partidas += processor.cuenta_partidas
        self.cuenta_facturas += processor.cuenta_facturas
        self.valor_total += processor.valor_total_factura
        self.peso_bruto_total += processor.peso_bruto_factura
        self.peso_neto_total += processor.peso_neto_factura
        
        # MF80 (Totales Manifiesto)
        # Sample: MF80000000000000000200000001099200000002000000009736
        # MF80 (4) + Val(12) + CantFact(4) + PB(12) + Flete(8) + PN(12)
        # Importante: El sample muestra que los totales NO tienen puntos y son enteros (centavos).
        val_int = int(round(processor.valor_total_factura * 100))
        pb_int = int(round(processor.peso_bruto_factura * 100))
        pn_int = int(round(processor.peso_neto_factura * 100))
        flete_int = 0 # Flete total
        
        lineas.append(
            f"MF80{val_int:012d}"
            f"{processor.cuenta_facturas:04d}"
            f"{pb_int:012d}"
            f"{flete_int:08d}"
            f"{pn_int:012d}"
        )
        self.cuenta_partidas += 1
        
        return lineas

    # (Mantenemos los métodos auxiliares: _obtener_configuracion_sistema, _obtener_datos_empresa, _escribir_archivo, etc.)
    def _obtener_configuracion_sistema(self, db): return ConfiguracionSistema(path_arch_transmision="/tmp", utilizar_nombre_generico_mainx30=True, utilizar_codigo_broker_cliente=False)
    def _obtener_datos_empresa(self, db): 
        empresa = db.query(GEmpresa).first()
        if not empresa:
            # Fallback safe defaults if no company config found
            return EmpresaDatos(broker="", responsable="", rfc="", tiene_linea_express="N", nombre_empresa="", manufacturer_id="", ftp_key="")
            
        return EmpresaDatos(
            broker=(empresa.broker_company or "")[:5], 
            responsable=(empresa.responsible or "")[:30],
            rfc=(empresa.rfc or "")[:13],
            tiene_linea_express="N",
            nombre_empresa=(empresa.name or "")[:40],
            manufacturer_id=(empresa.manufacturer_id or "")[:10],
            ftp_key=(empresa.ftp_key or "")[:10]
        )
    def _validar_datos_empresa(self, datos): pass
    def _generar_nombre_archivo(self, c, r, m): return f"{m}_Mainx30.dat"

    def _generar_linea_a(self, f, d):
        # Sample: A 26021203AKR   AKR       00SCSI
        broker = d.broker.strip()[:6]
        # Use ftp_key (password?)
        password = (d.ftp_key or "00SCSI")[:6] 
        return f"A {f}03{broker:<6}{broker:<10}{password}"

    def _escribir_archivo(self, ruta, lineas):
        Path(ruta).parent.mkdir(parents=True, exist_ok=True)
        with open(ruta, 'w', encoding='latin-1') as f: f.write('\r\n'.join(lineas))
    def _generar_mensaje_resultado(self, nombre): return f"Generado: {nombre}"
    def _agregar_error_validacion(self, partida, id_err, desc, sol, tipo):
        self.errores_validacion.append(ErrorValidacion(partida=partida, linea=0, descripcion=desc, soluciones=sol, identificador=tipo))