import uuid
import os
from contextlib import asynccontextmanager
from pathlib import Path

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse, FileResponse, RedirectResponse
from pydantic import BaseModel
from langfuse import Langfuse

from dotenv import load_dotenv  # ← Agregar este import

from api import services
from api.agent import Agent
from api.config import config

# Cargar variables de entorno
load_dotenv()

# Configurar Langfuse desde variables de entorno
langfuse = Langfuse(
    public_key=os.getenv("LANGFUSE_PUBLIC_KEY"),
    secret_key=os.getenv("LANGFUSE_SECRET_KEY"), 
    host=os.getenv("LANGFUSE_HOST")
)


# Mapeo completo de archivos a URLs públicas
PDF_PUBLIC_URLS = {
    # Disposiciones de CNBV
    "Disposiciones de carácter general aplicables a las casas de bolsa.pdf": "https://www.cnbv.gob.mx/Normatividad/Disposiciones%20de%20car%C3%A1cter%20general%20aplicables%20a%20las%20casas%20de%20bolsa.pdf",
    "Disposiciones de carácter general aplicables a las instituciones de crédito.pdf": "https://www.cnbv.gob.mx/Normatividad/Disposiciones%20de%20car%C3%A1cter%20general%20aplicables%20a%20las%20instituciones%20de%20cr%C3%A9dito.pdf",
    "Disposiciones de carácter general aplicables a las sociedades controladoras de grupos financieros y subcontroladoras que regulan las materias que corresponden de manera conjunta a las Comisio.pdf": "https://www.cnbv.gob.mx/Normatividad/Disposiciones%20de%20car%C3%A1cter%20general%20aplicables%20a%20las%20sociedades%20controladoras%20de%20grupos%20financieros%20y%20subcontroladoras%20que%20regulan%20las%20materias%20que%20corresponden%20de%20manera%20conjunta%20a%20las%20Comisiones%20Nacionales%20Supervisoras.pdf",
    "Disposiciones de carácter general aplicables a los fondos de inversión y a las personas que les prestan servicios.pdf": "https://www.cnbv.gob.mx/Normatividad/Disposiciones%20de%20car%C3%A1cter%20general%20aplicables%20a%20los%20fondos%20de%20inversi%C3%B3n%20y%20a%20las%20personas%20que%20les%20prestan%20servicios.pdf",
    "Ley para la Transparencia y Ordenamiento de los Servicios Financieros.pdf": "https://www.cnbv.gob.mx/Normatividad/Ley%20para%20la%20Transparencia%20y%20Ordenamiento%20de%20los%20Servicios%20Financieros.pdf",
    
    # Circulares CNBV adicionales
    "circular_servicios_de_inversion.pdf": "https://www.cnbv.gob.mx/Normatividad/Disposiciones%20de%20car%C3%A1cter%20general%20aplicables%20a%20las%20entidades%20financieras%20y%20dem%C3%A1s%20personas%20que%20proporcionen%20servicios%20de.pdf",
    "circular_unica_de_auditores_externos.pdf": "https://www.cnbv.gob.mx/Normatividad/Disposiciones%20de%20car%C3%A1cter%20general%20que%20establecen%20los%20requisitos%20que%20deber%C3%A1n%20cumplir%20los%20auditores%20y%20otros%20profesionales%20que.pdf",
    "ley_de_instituciones_de_Credito.pdf": "https://www.cnbv.gob.mx/Normatividad/Ley%20de%20Instituciones%20de%20Cr%C3%A9dito.pdf",
    
    # Circulares de Banxico
    "circular_13_2007.pdf": "https://www.banxico.org.mx/marco-normativo/normativa-emitida-por-el-banco-de-mexico/circular-13-2007/cobro-intereses-por-adelantad.html",
    "circular_13_2011.pdf": "https://www.banxico.org.mx/marco-normativo/normativa-emitida-por-el-banco-de-mexico/circular-13-2011/%7BBA4CBC28-A468-16C9-6F17-9EA9D7B03318%7D.pdf",
    "circular_14_2007.pdf": "https://www.banxico.org.mx/marco-normativo/normativa-emitida-por-el-banco-de-mexico/circular-14-2007/%7BFB726B6B-D523-56F5-F9B1-BE5B3B95A504%7D.pdf",
    "circular_17_2014.pdf": "https://www.banxico.org.mx/marco-normativo/normativa-emitida-por-el-banco-de-mexico/circular-17-2014/%7BF36CEF03-9441-2DBE-082C-0DF274903782%7D.pdf",
    "circular_1_2005.pdf": "https://www.banxico.org.mx/marco-normativo/normativa-emitida-por-el-banco-de-mexico/circular-1-2005/%7B5CA4BA75-FEA8-199C-F129-E8E6A73E84F3%7D.pdf",
    "circular_21_2009.pdf": "https://www.banxico.org.mx/marco-normativo/normativa-emitida-por-el-banco-de-mexico/circular-21-2009/%7B29285862-EDE0-567A-BAFB-D261406641A3%7D.pdf",
    "circular_22_2008.pdf": "https://www.banxico.org.mx/marco-normativo/normativa-emitida-por-el-banco-de-mexico/circular-22-2008/%7BF15C8A26-C92E-BE2B-9344-51EDAA3C9B68%7D.pdf",
    "circular_22_2010.pdf": "https://www.banxico.org.mx/marco-normativo/normativa-emitida-por-el-banco-de-mexico/circular-22-2010/%7B0D531F59-1001-4D67-D7B4-D5854DD07A58%7D.pdf",
    "circular_27_2008.pdf": "https://www.banxico.org.mx/marco-normativo/normativa-emitida-por-el-banco-de-mexico/circular-27-2008/%7BBC4333FE-070F-E727-199E-CA6BCF2CBA66%7D.pdf",
    "circular_34_2010.pdf": "https://www.banxico.org.mx/marco-normativo/normativa-emitida-por-el-banco-de-mexico/circular-34-2010/%7B0C55B906-6DB4-6B88-FED0-67987E9FB3CC%7D.pdf",
    "circular_35_2010.pdf": "https://www.banxico.org.mx/marco-normativo/normativa-emitida-por-el-banco-de-mexico/circular-35-2010/%7B74C5641C-ED98-53C7-F08B-A3C7BAE0D480%7D.pdf",
    "circular_36_2010.pdf": "https://www.banxico.org.mx/marco-normativo/normativa-emitida-por-el-banco-de-mexico/circular-36-2010/%7B26C55DE6-CC3A-3368-34FC-1A6C50B11130%7D.pdf",
    "circular_3_2012.pdf": "https://www.banxico.org.mx/marco-normativo/normativa-emitida-por-el-banco-de-mexico/circular-3-2012/%7B4E0281A4-7AD8-1462-BC79-7F2925F3171D%7D.pdf",
    "circular_4_2012.pdf": "https://www.banxico.org.mx/marco-normativo/normativa-emitida-por-el-banco-de-mexico/circular-4-2012/%7B97C62974-1C94-19AE-AB5A-D0D949A36247%7D.pdf",
    
    # CONDUSEF
    "circular_unica_de_condusef.pdf": "https://www.condusef.gob.mx/documentos/marco_legal/disposiciones-transparencia-if-sofom.pdf",
    "ley_para_regular_las_sociedades_de_informacion_crediticia.pdf": "https://www.condusef.gob.mx/documentos/marco_legal/disposiciones-transparencia-if-sofom.pdf",
    
    # Leyes federales
    "ley_federal_de_proteccion_de_datos_personales_en_posesion_de_los_particulares.pdf": "https://www.diputados.gob.mx/LeyesBiblio/pdf/LFPDPPP.pdf",
    "reglamento_de_la_ley_federal_de_proteccion_de_datos_personales_en_posesion_de_los_particulares.pdf": "https://www.diputados.gob.mx/LeyesBiblio/regley/Reg_LFPDPPP.pdf",
    
    # SharePoint Banorte
    "Modificaciones Recursos Procedencia Ilícita jul 25 PLD.pdf": "https://gfbanorte.sharepoint.com/:w:/r/sites/Formatosyplantillas/Documentos%20compartidos/Otros/Modificaciones%20Recursos%20Procedencia%20Il%C3%ADcita%20jul%2025%20PLD.docx?d=w6a941e9e2c26403ea41c12de35536516&csf=1&web=1&e=EHtc9b",
}


@asynccontextmanager
async def lifespan(_: FastAPI):
    await config.init_mongo_db()
    yield


app = FastAPI(lifespan=lifespan)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"], 
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
    expose_headers=["*"]
)

agent = Agent()

PDF_FOLDER = Path(__file__).parent / "agent" / "pdf"
PDF_FOLDER.mkdir(parents=True, exist_ok=True) 


@app.post("/api/v1/conversation")
async def create_conversation():
    conversation_id = uuid.uuid4()
    await services.create_conversation(conversation_id, agent.system_prompt)
    return {"conversation_id": conversation_id}


class Message(BaseModel):
    conversation_id: uuid.UUID
    prompt: str


@app.post("/api/v1/message")
async def send(message: Message):
    # Tracking básico del chat
    trace = langfuse.trace(
        name="rag_chat",
        session_id=str(message.conversation_id),
        input={"prompt": message.prompt}
    )
    
    def b64_sse(func):
        async def wrapper(*args, **kwargs):
            response_parts = []
            
            async for chunk in func(*args, **kwargs):
                if chunk.type == "text" and chunk.content:
                    response_parts.append(str(chunk.content))
                
                content = chunk.model_dump_json()
                data = f"data: {content}\n\n"
                yield data
            
            # Solo registrar input y output
            full_response = "".join(response_parts)
            trace.update(output={"response": full_response})

        return wrapper

    sse_stream = b64_sse(services.stream)
    generator = sse_stream(agent, message.prompt, message.conversation_id)
    return StreamingResponse(generator, media_type="text/event-stream")


@app.get("/api/pdf/{filename}")
async def get_pdf(filename: str):
    print(f"🔍 Solicitud PDF para: {filename}")
    
    if not filename.lower().endswith('.pdf'):
        print(f"❌ Archivo no es PDF: {filename}")
        raise HTTPException(status_code=400, detail="El archivo debe ser un PDF")
    
    if '..' in filename or ('/' in filename and not filename.startswith('http')) or '\\' in filename:
        print(f"❌ Nombre de archivo inválido: {filename}")
        raise HTTPException(status_code=400, detail="Nombre de archivo inválido")
    
    public_url = PDF_PUBLIC_URLS.get(filename)
    
    if public_url:
        print(f"✅ Redirigiendo a URL pública: {public_url}")
        return RedirectResponse(
            url=public_url,
            status_code=302,
            headers={
                "Cache-Control": "public, max-age=3600",
                "Access-Control-Allow-Origin": "*",
                "Access-Control-Allow-Methods": "GET, OPTIONS",
                "Access-Control-Allow-Headers": "*"
            }
        )
    
    pdf_path = PDF_FOLDER / filename
    
    if not pdf_path.exists():
        print(f"❌ PDF no encontrado: {pdf_path}")
        raise HTTPException(status_code=404, detail=f"PDF no encontrado. Archivo: {filename}")
    
    if not pdf_path.is_file():
        print(f"❌ No es un archivo: {pdf_path}")
        raise HTTPException(status_code=404, detail="El recurso no es un archivo")
    
    file_size = pdf_path.stat().st_size
    print(f"📄 Sirviendo archivo local: {filename} ({file_size} bytes)")
    
    if file_size == 0:
        print(f"❌ Archivo vacío: {pdf_path}")
        raise HTTPException(status_code=500, detail="El archivo PDF está vacío")
    
    return FileResponse(
        path=str(pdf_path),
        media_type="application/pdf",
        filename=filename,
        headers={
            "Content-Disposition": f"inline; filename={filename}", 
            "Content-Type": "application/pdf",
            "Cache-Control": "public, max-age=3600",
            "X-Frame-Options": "ALLOWALL",  
            "X-Content-Type-Options": "nosniff",
            "Access-Control-Allow-Origin": "*"
        }
    )


@app.get("/api/pdfs")
async def list_pdfs():
    try:
        pdf_files = []
        
        for filename, url in PDF_PUBLIC_URLS.items():
            pdf_files.append({
                "filename": filename,
                "size": "N/A (Público)",
                "url": f"/api/pdf/{filename}",
                "public_url": url,
                "type": "public"
            })
        
        local_files = []
        for pattern in ["*.pdf", "*.PDF"]:
            for file_path in PDF_FOLDER.glob(pattern):
                if file_path.is_file() and file_path.name not in PDF_PUBLIC_URLS:
                    local_files.append({
                        "filename": file_path.name,
                        "size": file_path.stat().st_size,
                        "url": f"/api/pdf/{file_path.name}",
                        "type": "local"
                    })
        
        pdf_files.extend(local_files)
        
        debug_info = {
            "current_working_directory": str(Path.cwd()),
            "pdf_folder_path": str(PDF_FOLDER.absolute()),
            "pdf_folder_exists": PDF_FOLDER.exists(),
            "public_urls_count": len(PDF_PUBLIC_URLS),
            "local_files_count": len(local_files),
            "public_files": list(PDF_PUBLIC_URLS.keys()),
        }
        
        return {
            "pdfs": pdf_files,
            "debug": debug_info,
            "total_pdfs": len(pdf_files)
        }
    except Exception as e:
        import traceback
        return {
            "error": str(e),
            "traceback": traceback.format_exc(),
            "debug": {
                "current_working_directory": str(Path.cwd()),
                "script_file_path": __file__ if '__file__' in globals() else "unknown"
            }
        }


@app.get("/api/pdf/{filename}/info")
async def get_pdf_info(filename: str):
    if not filename.lower().endswith('.pdf'):
        raise HTTPException(status_code=400, detail="El archivo debe ser un PDF")
    
    if '..' in filename or '/' in filename or '\\' in filename:
        raise HTTPException(status_code=400, detail="Nombre de archivo inválido")
    
    public_url = PDF_PUBLIC_URLS.get(filename)
    if public_url:
        return {
            "filename": filename,
            "size": "N/A",
            "size_mb": "N/A", 
            "modified": "N/A",
            "url": f"/api/pdf/{filename}",
            "public_url": public_url,
            "type": "public"
        }
    
    pdf_path = PDF_FOLDER / filename
    
    if not pdf_path.exists():
        raise HTTPException(status_code=404, detail="PDF no encontrado")
    
    if not pdf_path.is_file():
        raise HTTPException(status_code=404, detail="El recurso no es un archivo")
    
    try:
        file_stat = pdf_path.stat()
        return {
            "filename": filename,
            "size": file_stat.st_size,
            "size_mb": round(file_stat.st_size / (1024 * 1024), 2),
            "modified": file_stat.st_mtime,
            "url": f"/api/pdf/{filename}",
            "type": "local"
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error al obtener información del PDF: {str(e)}")


@app.get("/api/health")
async def health_check():
    return {
        "status": "healthy",
        "pdf_folder": str(PDF_FOLDER),
        "pdf_folder_exists": PDF_FOLDER.exists(),
        "public_urls_configured": len(PDF_PUBLIC_URLS)
    }