from flask import Flask, render_template, request, redirect, url_for, flash, session, Response
import os, re, json
from werkzeug.utils import secure_filename

from scripts.script1_evaluador import EvaluadorENS
from scripts.script2_diagnostico import generate_diagnostic
from scripts.script3_politicas import generate_policy
from scripts.script7_optimizador import calcular_estimacion
from scripts.script8_generar_informe import generar_pdf

from scripts.script4_map_recomendaciones import MAPPING
from scripts.script5_map_recomendaciones import MAPPING_5
from scripts.script6_map_recomendaciones import MAPPING_6

# Importar parsers
from parsers.auditor_parser import parsear_informe, CONTROLES_DESCRIPCION
from parsers.detector_parser import parsear_informe_incidentes, CONTROLES_DESCRIPCION_INC
from parsers.simulador_parser import parsear_informe_simulador, SIMULADOR_DESCRIPCIONES

app = Flask(__name__)
app.secret_key = 'cambiar_esta_clave'

# Carpeta para subidas (archivos .txt, .json, etc.)
UPLOAD_FOLDER = 'uploads'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

# Límites y extensiones permitidas para las subidas
MAX_CONTENT_LENGTH = 10 * 1024 * 1024  # 10 MB máximo
app.config['MAX_CONTENT_LENGTH'] = MAX_CONTENT_LENGTH
ALLOWED_EXTENSIONS = {'txt', 'json', 'log'}

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route('/')
def index():
    return render_template('index.html')


# --------------------------------------------------
# Script 1: Evaluador de Cumplimiento ENS Nivel Alto
# --------------------------------------------------
@app.route('/evaluar', methods=['GET', 'POST'])
def evaluar():
    evaluator = EvaluadorENS()

    if request.method == 'POST':
        # Procesar formulario
        respuestas = evaluator.save_respuestas(request.form)

        # Calcular estadísticas
        stats = evaluator.calculate_stats(respuestas)
        group_stats = evaluator.calculate_group_stats(respuestas)

        # Guardar en sesión
        session['evaluacion_controles'] = respuestas
        session['stats_ens'] = stats
        session['group_stats_ens'] = group_stats

        # Mostrar resultados
        return render_template(
            'resultado.html',
            respuestas=respuestas,
            stats=stats,
            group_stats=group_stats
        )

    # ==================== Sólo para GET ====================
    # Leemos de sesión las respuestas previas (si ya existen):
    respuestas_prev = session.get('evaluacion_controles', None)

    # Si existe respuestas_prev y NO viene ?edit=1, mostramos el resumen directamente
    if respuestas_prev is not None and request.args.get('edit') != '1':
        stats = session.get('stats_ens')
        group_stats = session.get('group_stats_ens')
        return render_template(
            'resultado.html',
            respuestas=respuestas_prev,
            stats=stats,
            group_stats=group_stats
        )

    # En cualquier otro caso (no hay respuestas o viene edit=1), mostramos el formulario:
    controles = evaluator.get_controles()
    return render_template(
        'evaluar.html',
        controles=controles,
        respuestas_anteriores=respuestas_prev
    )


@app.route('/evaluar/download')
def descargar_resumen():
    respuestas = session.get('evaluacion_controles')
    stats = session.get('stats_ens')
    group_stats = session.get('group_stats_ens')
    if not respuestas or not stats or not group_stats:
        flash('Antes debes completar la evaluación ENS.', 'warning')
        return redirect(url_for('evaluar'))

    evaluator = EvaluadorENS()
    texto = evaluator.format_resumen(respuestas, stats, group_stats)

    return Response(
        texto,
        mimetype='text/plain',
        headers={'Content-Disposition': 'attachment;filename=resumen_ens_nivel_alto.txt'}
    )


@app.route('/evaluar/reset')
def reset_evaluacion():
    session.pop('evaluacion_controles', None)
    session.pop('stats_ens', None)
    session.pop('group_stats_ens', None)
    flash('Se han borrado todas las respuestas. Puedes empezar de nuevo.', 'info')
    return redirect(url_for('evaluar'))


# --------------------------------------------------
# Script 2: Generador de Diagnóstico
# --------------------------------------------------
@app.route('/diagnostico', methods=['GET'])
def diagnostico():
    respuestas = session.get('evaluacion_controles')
    if not respuestas:
        flash('Primero debes completar la evaluación ENS.', 'warning')
        return redirect(url_for('evaluar'))

    # generate_diagnostic ahora devuelve un DataFrame ya listo para mostrar:
    df = generate_diagnostic(respuestas)
    tabla = df.to_dict(orient='records')

    # ————— Guarda JSON intermedio para Script 8 —————
    json_path = os.path.join(app.static_folder, 'datos_script2.json')
    with open(json_path, 'w', encoding='utf-8') as f:
        json.dump(tabla, f, ensure_ascii=False, indent=2)

    return render_template('diagnostico.html', tabla=tabla)

@app.route('/diagnostico/download')
def descargar_diagnostico():
    # Recuperamos del session la lista de controles evaluados
    respuestas = session.get('evaluacion_controles')
    if not respuestas:
        flash('Primero debes completar la evaluación ENS para generar diagnóstico.', 'warning')
        return redirect(url_for('evaluar'))

    # Generamos el DataFrame con nuestro nuevo script2_diagnostico
    df = generate_diagnostic(respuestas)

    # Convertimos el DataFrame a CSV en memoria
    csv_buffer = df.to_csv(index=False, sep=';', encoding='utf-8')

    # Enviamos la respuesta con Content-Disposition para forzar descarga
    return Response(
        csv_buffer,
        mimetype='text/csv',
        headers={'Content-Disposition': 'attachment;filename=diagnostico_ens.csv'}
    )


# --------------------------------------------------
# Script 3: Generador de Políticas
# --------------------------------------------------
@app.route('/politicas', methods=['GET', 'POST'])
def politicas():
    if request.method == 'POST':
        params   = request.form.to_dict(flat=True)
        selected = request.form.getlist('policies')
        resultados = []
        for key in selected:
            ruta_pol = generate_policy(key, params)  # {'yaml': 'static/...yml', 'txt': 'static/...txt'}
            resultados.append({
                'key': key,
                'ruta': ruta_pol
            })
        return render_template('politicas_resultado.html', resultados=resultados)

    return render_template('politicas.html')




# --------------------------------------------------
# Script 4: Auditor de Permisos y Accesos
# --------------------------------------------------
@app.route('/auditor_permisos', methods=['GET', 'POST'])
def auditor_permisos():
    vulnerabilidades = []

    if request.method == 'POST':
        # Validar archivo
        if 'informe' not in request.files:
            flash('Ningún archivo seleccionado', 'error')
            return redirect(request.url)

        file = request.files['informe']

        if file.filename == '':
            flash('Ningún archivo seleccionado', 'error')
            return redirect(request.url)

        if not allowed_file(file.filename):
            flash('Extensión no permitida. Solo .txt.', 'error')
            return redirect(request.url)

        # Guardar archivo
        filename = secure_filename(file.filename)
        filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        file.save(filepath)

        # Procesar informe
        with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
            for line in f:
                linea = line.strip()
                if not linea:
                    continue

                # saltamos líneas no relevantes
                if (
                    linea.startswith("Control ") or
                    linea.startswith("Verificar") or
                    linea.startswith("================") or
                    linea.lower().startswith("todos los usuarios") or
                    linea.lower().startswith("no hay") or
                    "deshabilitado" in linea.lower()
                ):
                    continue

                # buscamos en el MAPPING
                for patron, info in MAPPING.items():
                    match = False
                    # si el patrón parece regex (contiene metacaracteres o comienza por "ALERTA:")
                    if patron.startswith("ALERTA:") or "\\" in patron or patron.startswith("PASS_") or "INTENTOS_FALLIDOS" in patron:
                        if re.search(patron, linea):
                            match = True
                    else:
                        if linea.startswith(patron):
                            match = True

                    if match:
                        # extraer usuario si el mensaje lo incluye
                        usuario = None

                        # 1) caso Linux: contraseña que nunca expira
                        if "nunca expira" in patron.lower() and ":" in linea:
                            usuario = linea.split(":", 1)[1].strip()

                        # 2) caso Windows: Usuario con privilegios en grupo
                        # Windows: extraer el nombre de usuario de la última "palabra" de la línea
                        if "Usuario con privilegios" in patron:
                            # split por espacios, coge el último token
                            ultimo = linea.split()[-1]  
                            # puede llevar backslashes o slashes: quitamos todo antes de la última barra
                            usuario = ultimo.split("\\")[-1].split("/")[-1]


                        # formateamos descripción reemplazando {usuario} si lo hemos extraído
                        descripcion = info["descripcion"]
                        if usuario:
                            descripcion = descripcion.format(usuario=usuario)

                        vulnerabilidades.append({
                            "linea_original": linea,
                            "codigo_ens":     info["codigo_ens"],
                            "titulo":         info["titulo"],
                            "descripcion":    descripcion,
                            "referencia":     info["referencia"]
                        })
                        break  # dejamos de buscar patrones para esta línea

        if not vulnerabilidades:
            flash('No se encontraron alertas en el informe.', 'info')


        # ————— Guarda JSON intermedio para Script 8 —————
        json_path = os.path.join(app.static_folder, 'datos_script4.json')
        with open(json_path, 'w', encoding='utf-8') as f:
            json.dump(vulnerabilidades, f, ensure_ascii=False, indent=2)
        # —————————————————————————————————————————————

    return render_template('auditor_permisos.html',
                           vulnerabilidades_detalladas=vulnerabilidades)



# --------------------------------------------------
# Script 5: Detector de Incidentes ENS
# --------------------------------------------------
@app.route('/detector_incidentes', methods=['GET','POST'])
def detector_incidentes():
    informe_struct = {}
    if request.method == 'POST':
        # 1) recogemos el fichero
        file = request.files.get('logfile')
        if not file or file.filename == '':
            flash('No se seleccionó ningún archivo.', 'error')
            return redirect(request.url)
        if not allowed_file(file.filename):
            flash('Formato no válido. Solo .txt, .log o .json.', 'error')
            return redirect(request.url)

        filename = secure_filename(file.filename)
        path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        file.save(path)

        # 2) procesamos línea a línea

        with open(path, 'r', encoding='utf-8', errors='ignore') as f:
            text = f.read()

        # Extraer todos los bloques JSON (desde '{' hasta su '}' correspondiente)
        # Esta expresión no es perfecta para JSON anidado muy complejo, pero funciona
        # para tus bloques de un solo nivel
        bloques = re.findall(r'\{[\s\S]+?\}', text)

        for bloque in bloques:
            try:
                evt = json.loads(bloque)
            except:
                continue

            tipo = evt.get('tipo')
            ts   = evt.get('timestamp','')
            # Reconstruimos un JSON compacto para mostrarlo en pantalla
            msg  = json.dumps(evt, ensure_ascii=False)

            if tipo in MAPPING_5:
                info = MAPPING_5[tipo]
                informe_struct.setdefault(tipo, []).append({
                    "timestamp":      ts,
                    "mensaje":        msg,
                    "titulo":         info["titulo"],
                    "descripcion":    info["descripcion"],
                    "recomendacion":  info["recomendacion"],
                    "referencia":     info["referencia"]
                })


        if not informe_struct:
            flash('No se encontraron alertas reconocibles.', 'info')

        # ————— Guarda JSON intermedio para Script 8 —————
        json_path = os.path.join(app.static_folder, 'datos_script5.json')
        with open(json_path, 'w', encoding='utf-8') as f:
           # convertimos informe_struct (dict de listas) en lista de objetos
            flat = []
            for tipo, lst in informe_struct.items():
                flat.extend(lst)
            json.dump(flat, f, ensure_ascii=False, indent=2)
        # —————————————————————————————————————————————

    return render_template(
        'detector_incidentes.html',
        informe_struct=informe_struct,
    )

# --------------------------------------------------
# Script 6: Simulador de Ataques ENS
# --------------------------------------------------

@app.route('/simulador_ataques', methods=['GET', 'POST'])
def simulador_ataques():
    resultado_simulador = None
    if request.method == 'POST':
        if 'jsonfile' not in request.files:
            flash('No se ha seleccionado ningún archivo JSON de informe.', 'error')
            return redirect(request.url)

        file = request.files['jsonfile']
        if file.filename == '':
            flash('Nombre de archivo vacío.', 'error')
            return redirect(request.url)

        filename = secure_filename(file.filename)
        ext = filename.rsplit('.', 1)[-1].lower()
        if ext not in ALLOWED_EXTENSIONS:
            flash('Extensión no permitida. Solo .json o .txt que contenga JSON.', 'error')
            return redirect(request.url)

        filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        file.save(filepath)

        try:
            resultado_simulador = parsear_informe_simulador(filepath)
            if not resultado_simulador:
                flash('El informe JSON subido no contiene datos válidos.', 'info')
        except Exception as e:
            flash(f'Error al parsear el informe del simulador: {e}', 'error')

        # ————— Guarda JSON intermedio para Script 8 —————
        json_path = os.path.join(app.static_folder, 'datos_script6.json')
        with open(json_path, 'w', encoding='utf-8') as f:
            json.dump(resultado_simulador, f, ensure_ascii=False, indent=2)
        # —————————————————————————————————————————————

    return render_template(
        'simulador_ataques.html',
        descripciones=SIMULADOR_DESCRIPCIONES,
        resultado_simulador=resultado_simulador
    )

# --------------------------------------------------
# Script 7: Optimizador de Recursos ENS
# --------------------------------------------------
from scripts.script7_optimizador import calcular_estimacion

@app.route('/optimizador_recursos', methods=['GET', 'POST'])
def optimizador_recursos():
    """
    1) GET: muestra el formulario (empleados, técnicos, sedes, madurez, presupuesto).
    2) POST: procesa los datos + session['evaluacion_controles'] y llama a calcular_estimacion.
    """
    resultado = None

    # Si no existe la evaluación de Script 1, redirigimos al usuario a “/evaluar”
    if 'evaluacion_controles' not in session:
        flash('Primero debes completar la evaluación ENS (Script 1).', 'error')
        return redirect(url_for('evaluar'))

    if request.method == 'POST':
        try:
            empleados   = int(request.form.get('empleados', 0))
            tecnicos    = int(request.form.get('tecnicos', 0))
            sedes       = int(request.form.get('sedes', 0))
            madurez     = request.form.get('madurez', 'bajo').lower()
            presupuesto = float(request.form.get('presupuesto', 0))

            # Reconstruimos estado_controles desde session['evaluacion_controles']
            # Cada item es algo como: {'codigo': 'Op.acc.1', 'respuesta': 'cumple'/'parcial'/'no_cumple', 'porcentaje': None/int}
            respuestas = session['evaluacion_controles']
            estado_controles = {
                r['codigo']: (r['respuesta'] == 'cumple')
                for r in respuestas
            }

            # Llamada a la función principal de cálculo
            resultado = calcular_estimacion(
                empleados=empleados,
                tecnicos=tecnicos,
                sedes=sedes,
                madurez=madurez,
                presupuesto=presupuesto,
                estado_controles=estado_controles
            )
        except Exception as e:
            flash(f'Error al calcular estimación: {e}', 'error')

    # Renderizamos la plantilla. Si fue GET, “resultado” será None y sólo verá el formulario.
    # Si fue POST exitoso, “resultado” contendrá el diccionario con partidas/totales/plan.

        # ————— Guarda JSON intermedio para Script 8 —————
        json_path = os.path.join(app.static_folder, 'datos_script7.json')
        with open(json_path, 'w', encoding='utf-8') as f:
            json.dump(resultado, f, ensure_ascii=False, indent=2)
        # —————————————————————————————————————————————

    return render_template('optimizador_form.html', resultado=resultado)

# --------------------------------------------------
# Script 8: Generador de Informe ENS
# --------------------------------------------------

@app.route('/generar_informe', methods=['GET', 'POST'])
def generar_informe():
    if request.method == 'POST':
        empresa = request.form.get('empresa', '').strip()
        if not empresa:
            flash('Por favor, ingresa el nombre de tu empresa.', 'error')
            return redirect(request.url)
        
        # Guardar en sesión para futuras generaciones
        session['empresa_nombre'] = empresa
        
        # Generar PDF
        pdf_path = generar_pdf(empresa)
        
        # Devolver el PDF al navegador
        return redirect(url_for('static', filename=('informes/' + os.path.basename(pdf_path))))
    
    # GET: Mostrar formulario
    # Intentar obtener nombre de empresa de diferentes fuentes
    empresa_default = (
        session.get('empresa_nombre') or
        session.get('params_politicas', {}).get('company_name') or
        ''
    )
    
    return render_template('generar_informe.html', empresa_default=empresa_default)

# Mantener compatibilidad con la ruta anterior
@app.route('/informe_final')
def informe_final():
    return redirect(url_for('generar_informe'))



if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)
