import gradio as gr
import PyPDF2
import os
import re
import vertexai
from vertexai.generative_models import GenerativeModel, Part, SafetySetting
from difflib import SequenceMatcher

# --------------------
# CONFIGURACIÓN GLOBAL
# --------------------
generation_config = {
    "max_output_tokens": 8192,
    "temperature": 0,
    "top_p": 0.8,
}
safety_settings = [
    SafetySetting(
        category=SafetySetting.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
        threshold=SafetySetting.HarmBlockThreshold.OFF
    ),
    SafetySetting(
        category=SafetySetting.HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
        threshold=SafetySetting.HarmBlockThreshold.OFF
    ),
    SafetySetting(
        category=SafetySetting.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
        threshold=SafetySetting.HarmBlockThreshold.OFF
    ),
    SafetySetting(
        category=SafetySetting.HarmCategory.HARM_CATEGORY_HARASSMENT,
        threshold=SafetySetting.HarmBlockThreshold.OFF
    ),
]

def configurar_credenciales(json_path: str):
    """Configura credenciales de Google Cloud a partir de un archivo JSON."""
    os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = json_path

# -----------
# LECTURA PDF
# -----------
def extraer_texto(pdf_path: str) -> str:
    """
    Extrae el texto de todas las páginas de un PDF con PyPDF2.
    Retorna un string con todo el texto concatenado.
    """
    texto_total = ""
    with open(pdf_path, "rb") as f:
        lector = PyPDF2.PdfReader(f)
        for page in lector.pages:
            texto_total += page.extract_text() or ""
    return texto_total

# -----------
# PARSEO DE TEXTO
# -----------
def split_secciones(texto: str) -> (str, str):
    """
    Separa el texto en dos partes: la sección 'Preguntas' y la sección 'RESPUESTAS'.
    Busca las palabras 'Preguntas' y 'RESPUESTAS' ignorando mayúsculas y espacios al inicio.
    """
    match_preg = re.search(r'(?im)^\s*preguntas', texto)
    match_resp = re.search(r'(?im)^\s*respuestas', texto)
    
    if not match_preg or not match_resp:
        return (texto, "")
    
    start_preg = match_preg.end()  # Fin de "Preguntas"
    start_resp = match_resp.start()  # Inicio de "RESPUESTAS"
    
    texto_preguntas = texto[start_preg:start_resp].strip()
    texto_respuestas = texto[match_resp.end():].strip()
    return (texto_preguntas, texto_respuestas)

def parsear_enumeraciones(texto: str) -> dict:
    """
    Dado un texto que contiene enumeraciones de preguntas (por ejemplo, "1. 1- RTA1" o "2- RTA2"),
    separa cada número y su contenido.
    Retorna un dict: {"Pregunta 1": "contenido", "Pregunta 2": "contenido", ...}.
    Este patrón es flexible y tolera espacios al inicio y formatos creativos.
    Además, elimina duplicados al inicio de la respuesta (por ejemplo, "Durante Durante ...").
    """
    # Se utiliza un lookahead para dividir cada bloque cuando se encuentre una línea que empiece con un número,
    # un punto o guión y, opcionalmente, otro número con punto o guión.
    bloques = re.split(r'(?=^\s*\d+[\.\-]\s*(?:\d+[\.\-])?\s*)', texto, flags=re.MULTILINE)
    resultado = {}
    for bloque in bloques:
        bloque = bloque.strip()
        if not bloque:
            continue
        # Extraemos el número de la pregunta y el contenido.
        match = re.match(r'^\s*(\d+)[\.\-]\s*(?:\d+[\.\-])?\s*(.*)', bloque)
        if match:
            numero = match.group(1)
            contenido = match.group(2)
            # Si hay múltiples líneas, unimos las líneas adicionales.
            lineas = bloque.split("\n")
            if len(lineas) > 1:
                contenido_completo = " ".join([linea.strip() for linea in lineas[1:]])
                if contenido_completo:
                    contenido = contenido + " " + contenido_completo
            # Eliminar duplicados al inicio (por ejemplo, "Durante Durante ..." se convierte en "Durante ...")
            contenido = re.sub(r'^(\S+)(\s+\1)+\s+', r'\1 ', contenido)
            resultado[f"Pregunta {numero}"] = contenido.strip()
    return resultado

# ------------
# COMPARACIÓN Y ANÁLISIS
# ------------
def similar_textos(texto1: str, texto2: str) -> float:
    """Calcula la similitud entre dos textos (valor entre 0 y 1)."""
    return SequenceMatcher(None, texto1, texto2).ratio()

def comparar_preguntas_respuestas(dict_docente: dict, dict_alumno: dict) -> (str, list):
    """
    Compara las respuestas del docente (correctas) con las del alumno.
    Para cada pregunta:
      - Si no fue asignada se indica "No fue asignada".
      - Si fue asignada se calcula la similitud y se evalúa:
          * Correcta: ratio >= 0.85
          * Incompleta: 0.5 <= ratio < 0.85
          * Incorrecta: ratio < 0.5
    Devuelve:
      - Un string con la retroalimentación por pregunta.
      - Una lista de diccionarios con el análisis por pregunta (solo para las asignadas).
    """
    feedback = []
    analisis = []
    for pregunta, resp_correcta in dict_docente.items():
        correct_clean = " ".join(resp_correcta.split())
        resp_alumno_raw = dict_alumno.get(pregunta, "").strip()
        
        if not resp_alumno_raw:
            feedback.append(
                f"**{pregunta}**\n"
                f"Respuesta del alumno: No fue asignada.\n"
                f"Respuesta correcta: {correct_clean}\n"
            )
            analisis.append({"pregunta": pregunta, "asignada": False})
        else:
            alumno_clean = " ".join(resp_alumno_raw.split())
            ratio = similar_textos(alumno_clean.lower(), correct_clean.lower())
            if ratio >= 0.85:
                eval_text = "La respuesta es correcta."
                resultado = "correcta"
            elif ratio >= 0.5:
                eval_text = "La respuesta es incompleta. Se observa que faltan conceptos clave."
                resultado = "incompleta"
            else:
                eval_text = "La respuesta es incorrecta. No se refleja el mecanismo o concepto correcto."
                resultado = "incorrecta"
            feedback.append(
                f"**{pregunta}**\n"
                f"Respuesta del alumno: {alumno_clean}\n"
                f"Respuesta correcta: {correct_clean}\n"
                f"{eval_text}\n"
            )
            analisis.append({"pregunta": pregunta, "asignada": True, "resultado": resultado})
    return "\n".join(feedback), analisis

# -----------
# FUNCIÓN PRINCIPAL
# -----------
def revisar_examen(json_cred, pdf_docente, pdf_alumno):
    """
    Función generadora que:
      1. Configura credenciales.
      2. Extrae y parsea el contenido de los PDFs.
      3. Separa las secciones 'Preguntas' y 'RESPUESTAS'.
      4. Parsea las enumeraciones de cada sección (soportando formatos creativos).
      5. Compara las respuestas del alumno con las correctas.
      6. Llama a un LLM para generar un resumen final con retroalimentación.
    """
    yield "Cargando credenciales..."
    try:
        configurar_credenciales(json_cred.name)
        
        yield "Inicializando Vertex AI..."
        vertexai.init(project="deploygpt", location="us-central1")
        
        yield "Extrayendo texto del PDF del docente..."
        texto_docente = extraer_texto(pdf_docente.name)
        
        yield "Extrayendo texto del PDF del alumno..."
        texto_alumno = extraer_texto(pdf_alumno.name)
        
        yield "Dividiendo secciones (docente)..."
        preguntas_doc, respuestas_doc = split_secciones(texto_docente)
        
        yield "Dividiendo secciones (alumno)..."
        preguntas_alum, respuestas_alum = split_secciones(texto_alumno)
        
        yield "Parseando enumeraciones (docente)..."
        dict_preg_doc = parsear_enumeraciones(preguntas_doc)
        dict_resp_doc = parsear_enumeraciones(respuestas_doc)
        # Unir las respuestas correctas del docente
        dict_docente = {}
        for key in dict_preg_doc:
            dict_docente[key] = dict_resp_doc.get(key, "")
        
        yield "Parseando enumeraciones (alumno)..."
        dict_preg_alum = parsear_enumeraciones(preguntas_alum)
        dict_resp_alum = parsear_enumeraciones(respuestas_alum)
        # Unir las respuestas del alumno
        dict_alumno = {}
        for key in dict_preg_alum:
            dict_alumno[key] = dict_resp_alum.get(key, "")
        
        yield "Comparando preguntas y respuestas..."
        feedback_text, analisis = comparar_preguntas_respuestas(dict_docente, dict_alumno)
        if len(feedback_text.strip()) < 5:
            yield "No se encontraron preguntas o respuestas válidas."
            return
        
        # Generar resumen global utilizando el LLM (solo para preguntas asignadas)
        analisis_asignadas = [a for a in analisis if a.get("asignada")]
        resumen_prompt = f"""
A continuación se presenta el análisis por pregunta de un examen sobre la regulación del colesterol, considerando solo las preguntas asignadas al alumno:
{analisis_asignadas}
Con base en este análisis, genera un resumen del desempeño del alumno en el examen que incluya:
- Puntos fuertes: conceptos que el alumno ha comprendido correctamente.
- Puntos a reforzar: preguntas en las que la respuesta fue incompleta o incorrecta, indicando qué conceptos clave faltaron o se confundieron.
- Una recomendación general sobre si el alumno demuestra comprender los fundamentos o si necesita repasar el tema.
No incluyas en el análisis las preguntas que no fueron asignadas.
"""
        yield "Generando resumen final con LLM..."
        model = GenerativeModel(
            "gemini-1.5-pro-001",
            system_instruction=["Eres un profesor experto en bioquímica. Evalúa el desempeño del alumno basándote en los conceptos clave, sin inventar elementos adicionales."]
        )
        summary_part = Part.from_text(resumen_prompt)
        summary_resp = model.generate_content(
            [summary_part],
            generation_config=generation_config,
            safety_settings=safety_settings,
            stream=False
        )
        resumen_final = summary_resp.text.strip()
        final_result = f"{feedback_text}\n\n**Resumen del desempeño:**\n{resumen_final}"
        yield final_result
    except Exception as e:
        yield f"Error al procesar: {str(e)}"

# -----------------
# INTERFAZ DE GRADIO
# -----------------
interface = gr.Interface(
    fn=revisar_examen,
    inputs=[
        gr.File(label="Credenciales JSON"),
        gr.File(label="PDF del Docente"),
        gr.File(label="PDF del Alumno")
    ],
    outputs="text",
    title="Revisión de Exámenes (Preguntas/Respuestas enumeradas)",
    description=(
        "Sube las credenciales, el PDF del docente (con las preguntas y respuestas correctas) y el PDF del alumno. "
        "El sistema separa las secciones 'Preguntas' y 'RESPUESTAS', parsea las enumeraciones (soportando formatos creativos) "
        "y luego compara las respuestas. Se evalúa si el alumno comprende los conceptos fundamentales: si la respuesta está incompleta se indica qué falta, "
        "si es incorrecta se comenta por qué, y se omiten las preguntas no asignadas. Finalmente, se genera un resumen con recomendaciones."
    )
)

interface.launch(debug=True)