package com.escolapios.fctmongo.controller;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

import java.util.List;
import java.util.Optional;
import java.util.ArrayList; // 🚀 Añadido para el casaste de arrays
import org.springframework.http.MediaType;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.escolapios.fctmongo.entity.Alumno;
import com.escolapios.fctmongo.entity.Convenio;
import com.escolapios.fctmongo.entity.Documento;
import com.escolapios.fctmongo.service.DocumentoService;
import com.escolapios.fctmongo.service.ConvenioService;
import com.escolapios.fctmongo.dto.GenerarDocumentoContext;
import com.escolapios.fctmongo.repository.ConvenioRepository;
import com.escolapios.fctmongo.repository.DocumentoRepository;
import com.escolapios.fctmongo.service.EmailService;
import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/api/documentos")
@RequiredArgsConstructor
@CrossOrigin(origins = "*") 
public class DocumentoController {
    private final DocumentoService documentoService;
    private final ConvenioService convenioService;
    private final ConvenioRepository convenioRepository; 
    private final EmailService emailService;
    private final DocumentoRepository documentoRepository;
    @GetMapping
    public ResponseEntity<List<Documento>> listarTodos() {
        return ResponseEntity.ok(documentoService.obtenerTodos());
    }
    @GetMapping("/{id}")
    public ResponseEntity<Resource> descargar(@PathVariable String id) throws Exception {
        Resource resource = documentoService.descargar(id);
        return ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
                .body(resource);
    }
    @GetMapping("/pdf/{id}")
    public ResponseEntity<Resource> descargarPdf(@PathVariable String id) throws Exception {
        Resource resource = documentoService.descargarPdf(id);
        return ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_PDF) 
                .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + resource.getFilename() + "\"")
                .body(resource);
    }
    @GetMapping("/alumno/{alumnoId}")
    public ResponseEntity<List<Documento>> porAlumno(@PathVariable String alumnoId) { 
        return ResponseEntity.ok(documentoService.buscarPorAlumno(alumnoId));
    }
    @GetMapping("/convenio/{convenioId}")
    public ResponseEntity<List<Documento>> porConvenio(@PathVariable String convenioId) { 
        return ResponseEntity.ok(documentoService.buscarPorConvenio(convenioId));
    }
    /**
     * 📥 DESCARGA OFICIAL DE PDFS APROBADOS (Solo para profesores autorizados)
     * GET -> http://localhost:8080/api/documentos/descargar-oficial/{convenioId}
     */
    @GetMapping("/descargar-oficial/{convenioId}")
    public ResponseEntity<byte[]> descargarPdfOficialAprobado(@PathVariable String convenioId) {
        try {
            // 1. Validamos rigurosamente el estado del documento en MongoDB antes de leer el disco
            Optional<Convenio> convenioOpt = convenioRepository.findById(convenioId);
            if (convenioOpt.isEmpty()) {
                return new ResponseEntity<>(HttpStatus.NOT_FOUND);
            }       
            Convenio convenio = convenioOpt.get();
            // 🔒 BLINDAJE: Si el documento no está aprobado todavía, denegamos el acceso
            if (!"APROBADO".equals(convenio.getEstado())) {
                return new ResponseEntity<>(HttpStatus.FORBIDDEN); // Código 403 Sin autorización
            }
            // 2. Extraemos los bytes del PDF consolidado a través del contexto (ctx)
            byte[] pdfBytes = documentoService.obtenerBytesBorradorConvenio(convenioId);
            if (pdfBytes == null || pdfBytes.length == 0) {
                return new ResponseEntity<>(HttpStatus.NOT_FOUND);
            }
            // 3. Montamos las cabeceras de transferencia de flujos binarios
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_PDF);
            // Usamos "attachment" si deseas que se descargue al disco de forma directa
            // o "inline" si prefieres que se abra el lector de PDFs nativo del navegador
            headers.setContentDispositionFormData("attachment", "Convenio_Oficial_" + convenio.getNumeroAcuerdo() + ".pdf");
            return new ResponseEntity<>(pdfBytes, headers, HttpStatus.OK);
        } catch (Exception e) {
            System.err.println("❌ Error en la descarga del PDF oficial: " + e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
     /**
     * 🏢 ENDPOINT A: Ámbito de Convenio o de Ciclo (Anexos IV y V)
     * GET -> http://localhost:8080/api/documentos/anexo/global/{numeroAnexo}/{targetId}
     */
    @GetMapping("/anexo/global/{numeroAnexo}/{targetId}")
    public ResponseEntity<byte[]> descargarAnexoGlobal(@PathVariable int numeroAnexo, @PathVariable String targetId) {
        try {
            Convenio convenioDto = null; // 🔄 Cambiado a tu entidad unificada Convenio
            String nombrePlantilla = "anexo_" + numeroAnexo + "_template.docx";

            if (numeroAnexo == 4) {
                // El Anexo 4 requiere el convenio completo con su lista de alumnos incrustada nativamente
                convenioDto = convenioService.obtenerConvenioConDetallesPorId(targetId);
                
                // ⚡ CLAVE: Si se localiza el convenio, forzamos a Lombok a aplanar las variables {{ ... }} para el Word
                if (convenioDto != null) {
                    convenioDto.sincronizarCamposPlanosTemplate();
                }
            } else if (numeroAnexo == 5) {
                // El Anexo 5 es el programa formativo del Ciclo. 
                // Instanciamos un objeto Convenio vacío en memoria simulando el contenedor
                convenioDto = new Convenio(); 
                
                // ⚡ Sincronizamos para evitar NullPointerExceptions en las llaves que queden vacías
                convenioDto.sincronizarCamposPlanosTemplate();
            } else {
                return ResponseEntity.badRequest().build();
            }

            if (convenioDto == null) {
                return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
            }

            java.nio.file.Path baseDir = java.nio.file.Paths.get(System.getProperty("java.io.tmpdir"));
            
            // 🔄 El record GenerarDocumentoContext ahora acepta la entidad Convenio de forma nativa
            GenerarDocumentoContext ctx = new GenerarDocumentoContext(
                convenioDto, 
                "ANEXO_" + numeroAnexo, 
                nombrePlantilla, 
                baseDir
            );
            
            Documento documento = documentoService.generarDocumento(ctx);
            if (documento == null) {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
            }
            
            return enviarRespuestaPdf(documento, "Anexo_" + numeroAnexo);

        } catch (Exception e) {
            System.err.println("❌ Error descargando anexo global " + numeroAnexo + ": " + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }
    /**
     * 👥 ÁMBITO INDIVIDUAL DE ALUMNO: Procesa los Anexos V, VI, VIII y IX
     * GET -> http://localhost:8080/api/documentos/anexo/alumno/{numeroAnexo}/{convenioId}/{alumnoId}
     */
    @GetMapping("/anexo/alumno/{numeroAnexo}/{convenioId}/{alumnoId}")
    public ResponseEntity<byte[]> descargarAnexoAlumno(
            @PathVariable int numeroAnexo,
            @PathVariable String convenioId,
            @PathVariable String alumnoId) {
        try {
            // 🟢 Validación de los anexos individuales aceptados
            if (numeroAnexo != 5 && numeroAnexo != 6 && numeroAnexo != 8 && numeroAnexo != 9) {
                return ResponseEntity.badRequest().build();
            }

            // 1. Descargamos el convenio completo con sus colecciones incrustadas desde MongoDB
            Convenio convenioDto = convenioService.obtenerConvenioConDetallesPorId(convenioId);
            if (convenioDto == null) return ResponseEntity.notFound().build();

            // 2. ⚡ FILTRADO ATÓMICO NO-SQL: Dejamos únicamente al alumno de la petición URL en la entidad
            if (convenioDto.getAlumnos() != null) {
                convenioDto.getAlumnos().removeIf(a -> !a.getId().equals(alumnoId));
            }

            // 3. ⚡ CLAVE LOMBOK: Sincronizamos las propiedades calculadas planas en la raíz (ej: alumnoNombre)
            convenioDto.sincronizarCamposPlanosTemplate();

            // 4. Apuntamos a la plantilla específica del anexo correspondiente
            String nombrePlantilla = "anexo_" + numeroAnexo + "_alumno_template.docx";
            java.nio.file.Path baseDir = java.nio.file.Paths.get(System.getProperty("java.io.tmpdir"));
            
            // 🔄 El record GenerarDocumentoContext ahora recibe la entidad unificada de forma nativa
            GenerarDocumentoContext ctx = new GenerarDocumentoContext(
                convenioDto, 
                "ANEXO_" + numeroAnexo, 
                nombrePlantilla, 
                baseDir
            );
            
            Documento documento = documentoService.generarDocumento(ctx);
            if (documento == null) {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
            }
            
            return enviarRespuestaPdf(documento, "Anexo_" + numeroAnexo + "_Alumno");

        } catch (Exception e) {
            System.err.println("❌ Error descargando anexo individual " + numeroAnexo + " para alumno: " + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }

    /**
     * 🔍 HISTORIAL: Recupera todos los registros documentales vinculados a un convenio
     * GET -> http://localhost:8080/api/documentos/por-convenio/{convenioId}
     */
    @GetMapping("/por-convenio/{convenioId}")
    public ResponseEntity<List<Documento>> obtenerDocumentosPorConvenio(@PathVariable String convenioId) {
        try {
            // Buscamos de forma directa en MongoDB utilizando la propiedad convenioId
            List<Documento> historial = documentoRepository.findByConvenioId(convenioId);
            return ResponseEntity.ok(historial);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }

    /**
     * 📥 DESCARGA OFICIAL BLINDADA: Transfiere los bytes al profesor solo si está aprobado
     * GET -> http://localhost:8080/api/documentos/descargar-anexo-validado/{documentoId}
     */
    @GetMapping("/descargar-anexo-validado/{documentoId}")
    public ResponseEntity<byte[]> descargarAnexoValidado(@PathVariable String documentoId) {
        try {
            // 1. Buscamos el registro documental en MongoDB
            Optional<Documento> docOpt = documentoRepository.findById(documentoId);
            if (docOpt.isEmpty()) {
                return new ResponseEntity<>(HttpStatus.NOT_FOUND);
            }
            Documento documento = docOpt.get();

            // 🔒 BLINDAJE DE SEGURIDAD CORREGIDO: Evaluamos el 'estado', no la 'ruta'
            if (!"APROBADO".equals(documento.getEstado())) {
                System.err.println("🔒 [SEGURIDAD] Intento de descarga denegado. El documento " + documentoId + " está en estado: " + documento.getEstado());
                return new ResponseEntity<>(HttpStatus.FORBIDDEN); // Código 403 Forbidden
            }

            // 2. Re-compilamos los bytes usando la nueva entidad unificada Convenio
            Convenio convenioDto = convenioService.obtenerConvenioConDetallesPorId(documento.getConvenioId());
            if (convenioDto == null) {
                return new ResponseEntity<>(HttpStatus.NOT_FOUND);
            }
            
            // Si el documento original guardó un alumno específico, aplicamos el filtro en memoria
            if (documento.getAlumnos() != null && !documento.getAlumnos().isEmpty()) {
                String targetAlumnoId = documento.getAlumnos().get(0).getId();
                if (convenioDto.getAlumnos() != null) {
                    convenioDto.getAlumnos().removeIf(a -> !a.getId().equals(targetAlumnoId));
                }
            }

            // ⚡ CLAVE LOMBOK: Sincronizamos las propiedades planas en la raíz para las llaves {{ ... }}
            convenioDto.sincronizarCamposPlanosTemplate();

            // Re-ensamblamos el record contextual original apuntando a la entidad Convenio
            String nombrePlantilla = documento.getTipo().toLowerCase() + "_template.docx";
            java.nio.file.Path baseDir = java.nio.file.Paths.get(System.getProperty("java.io.tmpdir"));
            
            // 🔄 El record GenerarDocumentoContext ahora recibe la entidad de forma nativa
            GenerarDocumentoContext ctx = new GenerarDocumentoContext(convenioDto, documento.getTipo(), nombrePlantilla, baseDir);
            
            Documento documentoCompilado = documentoService.generarDocumento(ctx);

            if (documentoCompilado == null || documentoCompilado.getBytes() == null) {
                return new ResponseEntity<>(HttpStatus.NOT_FOUND);
            }

            // 3. Montamos las cabeceras HTTP para forzar la descarga del PDF al disco del cliente
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_PDF);
            headers.setContentDispositionFormData("attachment", documento.getNombreFichero());

            return new ResponseEntity<>(documentoCompilado.getBytes(), headers, HttpStatus.OK);

        } catch (Exception e) {
            System.err.println("❌ Fallo en la descarga del anexo autorizado: " + e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    // En tu DocumentoController.java
    @GetMapping("/pendientes-y-autorizados")
    public ResponseEntity<List<Documento>> obtenerDocumentosNoFirmados() {
        // Trae todos los documentos excepto los archivados (FIRMADOS)
        return ResponseEntity.ok(documentoRepository.findByEstadoNot("FIRMADO"));
    }
    /**
     * ✅ APROBAR DOCUMENTO
     * GET -> http://localhost:8080/api/documentos/validar/aprobar/{documentoId}
     */
    @GetMapping("/validar/aprobar/{documentoId}")
    public ResponseEntity<String> aprobarDocumento(@PathVariable String documentoId) {
        try {
            Optional<Documento> docOpt = documentoRepository.findById(documentoId);
            if (docOpt.isEmpty()) {
                return ResponseEntity.status(HttpStatus.NOT_FOUND)
                        .body("<h3>Error:</h3><p>El documento con ID " + documentoId + " no existe.</p>");
            }

            Documento doc = docOpt.get();
            doc.setEstado("APROBADO"); // 🟢 Cambia el estado en la colección de documentos
            documentoRepository.save(doc);

            System.out.println("✅ [MONGO] Documento " + documentoId + " marcado como APROBADO.");

            // Opcional: Aquí puedes añadir lógica para cambiar también el estado del convenio si lo requieres
            // convenioService.actualizarEstado(doc.getConvenioId(), "APROBADO_JEFE");

            String htmlResponse = "<div style='font-family:sans-serif; text-align:center; margin-top:50px;'>"
                    + "<h2 style='color:#198754;'>✔ ¡Documento Autorizado con Éxito!</h2>"
                    + "<p>El estado ha sido actualizado a <b>APROBADO</b> en el sistema.</p>"
                    + "</div>";
            return ResponseEntity.ok().body(htmlResponse);

        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error al aprobar: " + e.getMessage());
        }
    }

    /**
     * ❌ RECHAZAR DOCUMENTO
     * GET -> http://localhost:8080/api/documentos/validar/rechazar/{documentoId}
     */
    @GetMapping("/validar/rechazar/{documentoId}")
    public ResponseEntity<String> rechazarDocumento(@PathVariable String documentoId) {
        try {
            Optional<Documento> docOpt = documentoRepository.findById(documentoId);
            if (docOpt.isEmpty()) {
                return ResponseEntity.status(HttpStatus.NOT_FOUND)
                        .body("<h3>Error:</h3><p>El documento con ID " + documentoId + " no existe.</p>");
            }

            Documento doc = docOpt.get();
            doc.setEstado("RECHAZADO"); // 🔴 Cambia el estado en la colección de documentos
            documentoRepository.save(doc);

            System.out.println("❌ [MONGO] Documento " + documentoId + " marcado como RECHAZADO.");

            String htmlResponse = "<div style='font-family:sans-serif; text-align:center; margin-top:50px;'>"
                    + "<h2 style='color:#dc3545;'>✖ Documento Rechazado</h2>"
                    + "<p>El documento ha sido marcado como <b>RECHAZADO</b>. Se notificará al gestor.</p>"
                    + "</div>";
            return ResponseEntity.ok().body(htmlResponse);

        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error al rechazar: " + e.getMessage());
        }
    }
    // Convenio y Anexo 4
    @PostMapping()
    public ResponseEntity<Documento> generar(@RequestBody Documento request) throws IOException {
        final String BASE_DIR = "documentos/firmados"; // 🔄 Apuntamos a la ubicación final
        
        // 🔄 Cambiado a tu entidad unificada Convenio
        Convenio convenioDto = convenioService.obtenerConvenioConDetallesPorId(request.getConvenioId());   
        if (convenioDto == null) {
            throw new RuntimeException("No se encontraron los detalles completos para el Convenio ID: " + request.getConvenioId());
        }
        
        if (request.getCursoAcademico() != null && !request.getCursoAcademico().isBlank()) {
            convenioDto.setCursoAcademico(request.getCursoAcademico());
        }
        
        // ⚡ CLAVE LOMBOK: Forzamos el aplanamiento de variables {{ ... }} justo antes del procesamiento del Word
        convenioDto.sincronizarCamposPlanosTemplate();
        
        String tipoDoc = request.getTipo().toUpperCase(); // CONVENIO o ANEXO_4
        String plantilla = tipoDoc.toLowerCase() + ".docx";
        
        // 🔄 El record GenerarDocumentoContext ahora acepta la entidad unificada de forma nativa
        GenerarDocumentoContext ctx = new GenerarDocumentoContext(
            convenioDto, 
            tipoDoc, 
            plantilla, 
            Path.of(BASE_DIR)
        );
        
        // 1. ⚡ Invocación única del motor de plantillas
        Documento documentoGenerado = documentoService.generarDocumento(ctx);
        if (documentoGenerado == null) {
            throw new RuntimeException("Error interno al instanciar el archivo binario.");
        }
        
        // 2. 📝 Homogeneización de metadatos para la base de datos y la tabla JS
        documentoGenerado.setConvenioId(request.getConvenioId());
        documentoGenerado.setCursoAcademico(convenioDto.getCursoAcademico());
        documentoGenerado.setTipo(tipoDoc);
        documentoGenerado.setEstado("PENDIENTE"); // 🔒 Bloqueado hasta validación del Jefe de Estudios
        documentoGenerado.setFechaGeneracion(java.time.LocalDateTime.now());
        documentoGenerado.setRuta(BASE_DIR + "/");
        
        // Asignamos el nombre del fichero correcto según corresponda
        if ("CONVENIO".equals(tipoDoc)) {
            documentoGenerado.setNombreFichero("Convenio_" + convenioDto.getNumeroAcuerdo() + ".pdf");
        } else {
            documentoGenerado.setNombreFichero("Anexo_4_" + convenioDto.getNumeroAcuerdo() + ".pdf");
        }
        
        // Copiamos la lista completa de alumnos vinculados al ser documentos de ámbito colectivo
        if (convenioDto.getAlumnos() != null) {
            documentoGenerado.setAlumnos(new ArrayList<>(convenioDto.getAlumnos()));
        }
        
        // 3. 💾 Persistencia definitiva y controlada en MongoDB
        Documento docGuardado = documentoRepository.save(documentoGenerado);
        System.out.println("💾 [MONGO] Documento de tipo general [" + tipoDoc + "] creado con ID: " + docGuardado.getId());
        
        // 4. 📧 Despacho del correo con el ID del documento para la doble acción
        try {
            byte[] pdfBytes = docGuardado.getBytes();
            String nombreEmpresa = (convenioDto.getEmpresa() != null) ? convenioDto.getEmpresa().getNombre() : "Empresa no especificada";
            
            emailService.enviarSolicitudAprobacion(
                convenioDto.getNumeroAcuerdo(), 
                nombreEmpresa, 
                pdfBytes, 
                docGuardado.getId() + "",
                docGuardado.getTipo()
            );
        } catch (Exception e) {
            System.err.println("⚠️ Documento guardado pero falló el envío del email: " + e.getMessage());
        }
        
        // Retornamos el documento persistido para que el cliente conozca su nuevo ID NoSQL
        return ResponseEntity.ok(docGuardado);
    }
    /**
     * 🟢 POST -> http://localhost:8080/api/documentos/anexo5
     * Generación masiva del Anexo 5 pasando una lista de IDs de convenio de forma segura en el BODY
     */
    @PostMapping("/anexo5")
    @CrossOrigin(origins = "*") // 🟢 OBLIGATORIO: Permite que tu frontend rebotado transmita payloads masivos JSON
    public ResponseEntity<String> solicitarAprobacionAnexo5(@RequestBody List<String> convenioIds) { // 🟢 CAMBIADO: @RequestBody en lugar de @RequestParam
        try {
            if (convenioIds == null || convenioIds.isEmpty()) {
                System.err.println("⚠️ [POST ANEXO 5] Rechazado: La lista de IDs enviada en el Body llegó vacía.");
                return ResponseEntity.badRequest().body("Debe proporcionar al menos un ID de convenio en el cuerpo.");
            }

            System.out.println("\n🔥 ====== [INICIO: POST ANEXO 5 COLECTIVO DESDE BODY JSON] ======");
            System.out.println("  -> 📦 Ráfaga masiva NoSQL detectada. IDs recibidos en Body: " + convenioIds.size());

            // 1. Recuperamos y consolidamos todos los convenios solicitados desde el repositorio nativo
            List<Convenio> listaConveniosDto = new ArrayList<>();
            List<Alumno> todosLosAlumnos = new ArrayList<>();
            StringBuilder acuerdosStringBuilder = new StringBuilder();

            for (String id : convenioIds) {
                if (id == null || id.isBlank()) continue;
                
                Convenio convenio = convenioService.obtenerConvenioConDetallesPorId(id);
                if (convenio != null) {
                    // ⚡ CLAVE LOMBOK: Aplanamos las propiedades en memoria por si el motor lee metadatos de cabecera
                    convenio.sincronizarCamposPlanosTemplate();
                    listaConveniosDto.add(convenio);
                    
                    if (convenio.getAlumnos() != null) {
                        todosLosAlumnos.addAll(convenio.getAlumnos());
                    }
                    
                    if (acuerdosStringBuilder.length() > 0) acuerdosStringBuilder.append(", ");
                    acuerdosStringBuilder.append(convenio.getNumeroAcuerdo());
                }
            }

            if (listaConveniosDto.isEmpty()) {
                System.err.println("❌ [POST ANEXO 5] Fallo: No se localizaron documentos reales para esos IDs en MongoDB.");
                return ResponseEntity.status(HttpStatus.NOT_FOUND).body("No se localizaron convenios válidos.");
            }

            // 🟢 CORRECCIÓN DE GETTER: Sincronizado con la propiedad cruda '.getCurso()' de tu MongoDB Compass
            String cursoAcademico = listaConveniosDto.get(0).getCursoAcademico() != null ? listaConveniosDto.get(0).getCursoAcademico() : "";
            Path directorioFinal = Paths.get("documentos", "firmados");

            System.out.println("  -> 🎯 Compilando plantilla colectiva con el periodo escolar: [" + cursoAcademico + "]");
            System.out.println("  -> 🏢 Relación resumida de Acuerdos a firmar: [" + acuerdosStringBuilder.toString() + "]");

            // 2. ⚡ Invocamos tu servicio especializado de POI
            Documento documentoFinal = documentoService.generarDocumentoColectivoAnexo5(
                listaConveniosDto, 
                "anexo_5.docx", 
                cursoAcademico, 
                directorioFinal
            );

            if (documentoFinal == null) {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error al generar el Anexo 5 físico.");
            }

            // 3. Homogeneizamos el registro NoSQL para que encaje con la tabla del Front
            documentoFinal.setTipo("ANEXO_5");
            documentoFinal.setNombreFichero("Anexo_5_Colectivo_" + System.currentTimeMillis() + ".pdf");
            documentoFinal.setEstado("PENDIENTE"); // 🔒 Nace bloqueado para revisión
            documentoFinal.setRuta(directorioFinal.toString() + "/");
            documentoFinal.setFechaGeneracion(java.time.LocalDateTime.now());
            documentoFinal.setCursoAcademico(cursoAcademico);
            documentoFinal.setAlumnos(todosLosAlumnos);
            documentoFinal.setConvenioId("COLECTIVO"); 

            // 4. Único guardado físico controlado en base de datos de MongoDB
            Documento docGuardado = documentoRepository.save(documentoFinal);
            System.out.println("💾 [MONGO] Anexo 5 Colectivo consolidado en NoSQL con ID de documento: " + docGuardado.getId());

            // 5. Despachamos el correo electrónico adaptado a la multi-empresa
            try {
                byte[] pdfBytes = docGuardado.getBytes();
                String listaAcuerdos = acuerdosStringBuilder.toString();
                
                emailService.enviarSolicitudAprobacion(
                    listaAcuerdos, 
                    "Múltiples Empresas (Anexo 5 Colectivo)", 
                    pdfBytes, 
                    docGuardado.getId() + "",
                    docGuardado.getTipo()
                );
                System.out.println("📨 [EMAIL] Solicitud colectiva notificada con éxito al Jefe de Estudios.");
            } catch (Exception e) {
                System.err.println("⚠️ [EMAIL WARNING] Anexo 5 guardado pero falló el envío del email: " + e.getMessage());
            }

            System.out.println("✅ ====== [FIN: POST ANEXO 5 CON UNIFICACIÓN COMPLETA] ======\n");
            return ResponseEntity.ok("OK");

        } catch (Exception e) {
            System.err.println("❌ Error crítico en endpoint POST anexo5: " + e.getMessage());
            e.printStackTrace();
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error al procesar el Anexo 5 Colectivo.");
        }
    }

    /**
     * 🟢 POST -> http://localhost:8080/api/documentos/anexo6/{alumnoId}
     */
    @PostMapping("/anexo6/{alumnoId}")
    public ResponseEntity<String> solicitarAprobacionAnexo6(
            @PathVariable String alumnoId,
            @RequestParam String convenioId) {
        try {
            // 🔄 Cambiado a tu entidad unificada Convenio
            Convenio convenioDto = convenioService.obtenerConvenioConPlanFormacionAnexo6(convenioId);
            if (convenioDto == null) {
                return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Convenio no localizado.");
            }

            // ⚡ CLAVE LOMBOK: Forzamos el aplanamiento de variables {{ ... }} para el Word
            convenioDto.sincronizarCamposPlanosTemplate();

            // 1. Configurar el contexto apuntando ya a la ubicación definitiva
            Path directorioFinal = Paths.get("documentos", "firmados");
            
            // 🔄 El record GenerarDocumentoContext ahora acepta la entidad unificada
            GenerarDocumentoContext ctx = new GenerarDocumentoContext(
                convenioDto, "ANEXO_6", "anexo_6.docx", directorioFinal
            );

            // 2. ⚡ Invocamos tu servicio especializado (Requiere ajustar su firma en el Service para recibir Convenio)
            Documento documentoFinal = documentoService.generarDocumentoAnexo6(ctx);
            if (documentoFinal == null) {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error al generar el archivo físico.");
            }

            // 3. Modificamos y homogeneizamos el registro para MongoDB
            documentoFinal.setConvenioId(convenioId);
            documentoFinal.setCursoAcademico(convenioDto.getCursoAcademico());
            documentoFinal.setTipo("ANEXO_6");
            documentoFinal.setNombreFichero("Anexo_6_" + convenioDto.getNumeroAcuerdo() + ".pdf");
            documentoFinal.setEstado("PENDIENTE"); // 🔒 Nace bloqueado en el Front
            documentoFinal.setRuta(directorioFinal.toString() + "/");
            documentoFinal.setFechaGeneracion(java.time.LocalDateTime.now());

            // Filtramos el alumno destino para guardarlo de forma segura en NoSQL
            List<Alumno> listaAlumnos = new ArrayList<>();
            if (convenioDto.getAlumnos() != null) {
                convenioDto.getAlumnos().stream()
                    .filter(a -> a.getId().equals(alumnoId))
                    .findFirst()
                    .ifPresent(listaAlumnos::add);
            }
            documentoFinal.setAlumnos(listaAlumnos);

            // 4. Único guardado físico controlado en base de datos
            Documento docGuardado = documentoRepository.save(documentoFinal);
            System.out.println("💾 [MONGO] Anexo 6 creado con ID: " + docGuardado.getId() + " y estado: " + docGuardado.getEstado());

            // 5. Despachamos el correo electrónico con los parámetros dinámicos corregidos
            try {
                byte[] pdfBytes = docGuardado.getBytes();
                String nombreEmpresa = (convenioDto.getEmpresa() != null) ? convenioDto.getEmpresa().getNombre() : "Empresa no especificada";
                
                emailService.enviarSolicitudAprobacion(
                    convenioDto.getNumeroAcuerdo(), 
                    nombreEmpresa, 
                    pdfBytes, 
                    docGuardado.getId() + "",
                    docGuardado.getTipo()
                );
            } catch (Exception e) {
                System.err.println("⚠️ Documento guardado pero falló el envío del email: " + e.getMessage());
            }

            return ResponseEntity.ok("OK");

        } catch (Exception e) {
            System.err.println("❌ Error en anexo6: " + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error al procesar el Anexo 6.");
        }
    }

    /**
     * 🟢 POST -> http://localhost:8080/api/documentos/anexo8/{alumnoId}
     */
    @PostMapping("/anexo8/{alumnoId}")
    public ResponseEntity<String> solicitarAprobacionAnexo8(
            @PathVariable String alumnoId,
            @RequestParam String convenioId) {
        try {
            // 🔄 Cambiado a tu entidad unificada Convenio
            Convenio convenioDto = convenioService.obtenerConvenioConDetallesPorId(convenioId);
            if (convenioDto == null) {
                return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Convenio no localizado.");
            }

            // ⚡ CLAVE LOMBOK: Forzamos el aplanamiento de variables {{ ... }} para el Word
            convenioDto.sincronizarCamposPlanosTemplate();

            Path directorioFinal = Paths.get("documentos", "firmados");
            
            // 🔄 El record GenerarDocumentoContext ahora acepta la entidad unificada
            GenerarDocumentoContext ctx = new GenerarDocumentoContext(
                convenioDto, "ANEXO_8", "anexo_8.docx", directorioFinal
            );

            // 2. ⚡ Invocamos tu servicio especializado (Requiere ajustar su firma en el Service para recibir Convenio)
            Documento documentoFinal = documentoService.generarDocumentoAnexo8(ctx);
            if (documentoFinal == null) {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error al generar el Anexo 8 físico.");
            }
            // 3. Modificamos y homogeneizamos el registro para MongoDB
            documentoFinal.setConvenioId(convenioId);
            documentoFinal.setCursoAcademico(convenioDto.getCursoAcademico());
            documentoFinal.setTipo("ANEXO_8");
            documentoFinal.setNombreFichero("Anexo_8_" + convenioDto.getNumeroAcuerdo() + ".pdf");
            documentoFinal.setEstado("PENDIENTE"); // 🔒 Nace bloqueado en el Front
            documentoFinal.setRuta(directorioFinal.toString() + "/");
            documentoFinal.setFechaGeneracion(java.time.LocalDateTime.now());

            // 🟢 CORRECCIÓN: Tipado genérico estricto <Alumno> para evitar warnings de compilación
            List<Alumno> listaAlumnos = new ArrayList<>();
            if (convenioDto.getAlumnos() != null) {
                convenioDto.getAlumnos().stream()
                    .filter(a -> a.getId().equals(alumnoId))
                    .findFirst()
                    .ifPresent(listaAlumnos::add);
            }
            documentoFinal.setAlumnos(listaAlumnos);

            // 4. Único guardado físico controlado en base de datos
            Documento docGuardado = documentoRepository.save(documentoFinal);
            System.out.println("💾 [MONGO] Anexo 8 creado con ID: " 
                                + docGuardado.getId() + " y estado: " + docGuardado.getEstado());

            // 5. Despachamos el correo electrónico con los parámetros dinámicos corregidos
            try {
                byte[] pdfBytes = docGuardado.getBytes();
                String nombreEmpresa = (convenioDto.getEmpresa() != null) ? convenioDto.getEmpresa().getNombre() : "Empresa no especificada";
                
                emailService.enviarSolicitudAprobacion(
                    convenioDto.getNumeroAcuerdo(),
                    nombreEmpresa,
                    pdfBytes,
                    docGuardado.getId() + "",
                    docGuardado.getTipo()
                );
            } catch (Exception e) {
                System.err.println("⚠️ Documento guardado pero falló el envío del email: " + e.getMessage());
            }

            return ResponseEntity.ok("OK");

        } catch (Exception e) {
            // 🟢 CORRECCIÓN: Formateo limpio y ordenado de las llaves de cierre estructurales
            System.err.println("❌ Error en anexo8: " + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error al procesar el Anexo 8.");
        }
    }
    /**
     * 🟢 POST -> http://localhost:8080/api/documentos/anexo9/{alumnoId}
     */
    @PostMapping("/anexo9/{alumnoId}")
    @CrossOrigin(origins = "*") // ⚡ Blindaje CORS activo para evitar el 'Failed to fetch' desde el frontal
    public ResponseEntity<String> solicitarAprobacionAnexo9(
            @PathVariable String alumnoId,
            @RequestParam String convenioId) {
        try {
            System.out.println("📡 [BACKEND] Recibida solicitud para generar Anexo 9. Alumno: " + alumnoId + " | Convenio: " + convenioId);
            
            // 🔄 Recuperamos tu entidad unificada Convenio con todos sus detalles NoSQL
            Convenio convenioDto = convenioService.obtenerConvenioConDetallesPorId(convenioId);
            if (convenioDto == null) {
                return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Convenio no localizado.");
            }

            // ⚡ CLAVE LOMBOK: Forzamos el aplanamiento de variables {{ ... }} para el Word
            convenioDto.sincronizarCamposPlanosTemplate();

            Path directorioFinal = Paths.get("documentos", "firmados");
            
            // 🔄 El record GenerarDocumentoContext acepta la entidad unificada
            GenerarDocumentoContext ctx = new GenerarDocumentoContext(
                convenioDto, "ANEXO_9", "anexo_9.docx", directorioFinal
            );

            // 2. ⚡ Invocamos tu servicio especializado para el Anexo 9
            Documento documentoFinal = documentoService.generarDocumentoAnexo9(ctx);
            if (documentoFinal == null) {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error al generar el Anexo 9 físico.");
            }

            // 3. Modificamos y homogeneizamos el registro para MongoDB
            documentoFinal.setConvenioId(convenioId);
            documentoFinal.setCursoAcademico(convenioDto.getCursoAcademico());
            documentoFinal.setTipo("ANEXO_9");
            documentoFinal.setNombreFichero("Anexo_9_" + convenioDto.getNumeroAcuerdo() + ".pdf");
            documentoFinal.setEstado("PENDIENTE"); // 🔒 Nace bloqueado en el Front
            documentoFinal.setRuta(directorioFinal.toString() + "/");
            documentoFinal.setFechaGeneracion(java.time.LocalDateTime.now());

            // 🟢 CONTROL: Tipado genérico estricto <Alumno> aislando exclusivamente al alumno destino
            List<Alumno> listaAlumnos = new ArrayList<>();
            if (convenioDto.getAlumnos() != null) {
                convenioDto.getAlumnos().stream()
                    .filter(a -> a.getId().equals(alumnoId))
                    .findFirst()
                    .ifPresent(listaAlumnos::add);
            }
            documentoFinal.setAlumnos(listaAlumnos);

            // 4. Único guardado físico controlado en la colección 'documentos' de MongoDB
            Documento docGuardado = documentoRepository.save(documentoFinal);
            System.out.println("💾 [MONGO] Anexo 9 creado con ID: " 
                                + docGuardado.getId() + " y estado: " + docGuardado.getEstado());

            // 5. Despachamos el correo electrónico con el PDF adjunto al Jefe de Estudios
            try {
                byte[] pdfBytes = docGuardado.getBytes();
                String nombreEmpresa = (convenioDto.getEmpresa() != null) ? convenioDto.getEmpresa().getNombre() : "Empresa no especificada";
                
                emailService.enviarSolicitudAprobacion(
                    convenioDto.getNumeroAcuerdo(),
                    nombreEmpresa,
                    pdfBytes,
                    docGuardado.getId() + "",
                    docGuardado.getTipo()
                );
                System.out.println("✉️ [EMAIL] Correo del Anexo 9 enviado con éxito.");
            } catch (Exception e) {
                System.err.println("⚠️ Documento guardado pero falló el envío del email: " + e.getMessage());
            }

            return ResponseEntity.ok("OK");

        } catch (Exception e) {
            System.err.println("❌ Error en anexo9: " + e.getMessage());
            e.printStackTrace();
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error al procesar el Anexo 9.");
        }
    }

     /**
     * 💾 SUBIDA MULTIPART RELATIVA: Almacena el PDF firmado dentro de documentos/firmados/ y actualiza MongoDB
     * POST -> http://localhost:8080/api/documentos/subir-firmado/{convenioId}
     */
    @PostMapping("/subir-firmado/{convenioId}")
    public ResponseEntity<String> subirPdfFirmado(
            @PathVariable String convenioId,
            @RequestParam("file") org.springframework.web.multipart.MultipartFile file) {
        try {
            // 1. Validamos la existencia del convenio NoSQL
            Optional<Convenio> convenioOpt = convenioRepository.findById(convenioId);
            if (convenioOpt.isEmpty()) {
                return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Convenio no localizado.");
            }
            Convenio convenio = convenioOpt.get();

            // 2. Comprobamos que el archivo recibido sea un PDF real
            if (file.isEmpty() || !"application/pdf".equals(file.getContentType())) {
                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("El archivo debe ser un PDF válido.");
            }

            // 3. 🟢 RUTA RELATIVA: Definimos la carpeta 'documentos/firmados' apuntando a la raíz del proyecto
            java.nio.file.Path carpetaDestino = java.nio.file.Paths.get("documentos", "firmados");
            
            // Si la estructura de carpetas no existe físicamente en el disco, Spring la creará automáticamente
            if (java.nio.file.Files.notExists(carpetaDestino)) {
                java.nio.file.Files.createDirectories(carpetaDestino);
            }

            // 4. Estructuramos un nombre único y limpio para el archivo físico (ej: Convenio_Firmado_12-CM_1718640000.pdf)
            String nombreArchivoFinal = "Convenio_Firmado_" + convenio.getNumeroAcuerdo() + "_" + System.currentTimeMillis() + ".pdf";
            
            // 5. Fusionamos la ruta de la carpeta con el nombre del archivo
            java.nio.file.Path rutaArchivoCompleta = carpetaDestino.resolve(nombreArchivoFinal);

            // 6. Transferimos y escribimos los bytes binarios del archivo al disco del servidor
            java.nio.file.Files.copy(file.getInputStream(), rutaArchivoCompleta, java.nio.file.StandardCopyOption.REPLACE_EXISTING);

            // 7. Cambiamos el estado del convenio a "FIRMADO"
            convenio.setEstado("FIRMADO");
            
            // Guardamos la ruta relativa limpia de cara a futuras descargas (ej: "documentos/firmados/Convenio_Firmado_12-CM_xxx.pdf")
            String rutaParaBbdd = rutaArchivoCompleta.toString().replace("\\", "/"); // Normalizamos barras para Linux/Windows
            
            convenio.setObservaciones(convenio.getObservaciones() + "\n[SISTEMA]: PDF Firmado archivado de forma relativa en: " + rutaParaBbdd);
            
            convenioRepository.save(convenio); // Sincronizamos los metadatos en MongoDB

            return ResponseEntity.ok("Archivo guardado de forma relativa y estado consolidado.");

        } catch (Exception e) {
            System.err.println("❌ Fallo crítico al archivar el documento firmado de forma relativa: " + e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error al archivar el archivo.");
        }
    }

    // Método auxiliar reutilizable para inyectar cabeceras binarias PDF
    private ResponseEntity<byte[]> enviarRespuestaPdf(Documento doc, String prefix) {
        if (doc == null || doc.getBytes() == null) return ResponseEntity.notFound().build();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_PDF);
        headers.setContentDispositionFormData("inline", prefix + "_" + System.currentTimeMillis() + ".pdf");
        return new ResponseEntity<>(doc.getBytes(), headers, HttpStatus.OK);
    }

}
