VerificationController.java

package com.wavii.controller;

import com.wavii.model.User;
import com.wavii.model.VerificationRequest;
import com.wavii.model.enums.Role;
import com.wavii.model.enums.VerificationStatus;
import com.wavii.repository.UserRepository;
import com.wavii.repository.VerificationRequestRepository;
import com.wavii.service.EmailService;
import com.wavii.service.OdooService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;

@RestController
@RequestMapping("/api/verification")
@RequiredArgsConstructor
@Slf4j
public class VerificationController {

    private static final String UPLOAD_DIR = "/app/uploads/verifications/";
    private static final String PDF_MIME_TYPE = "application/pdf";

    private final VerificationRequestRepository verificationRequestRepository;
    private final UserRepository userRepository;
    private final OdooService odooService;
    private final EmailService emailService;

    @Value("${wavii.app.base-url:http://localhost:8080}")
    private String appBaseUrl;

    @Value("${odoo.webhook-secret:}")
    private String odooWebhookSecret;

    @PostMapping("/upload-document")
    public ResponseEntity<?> uploadDocument(
            @RequestParam("document") MultipartFile file,
            @AuthenticationPrincipal User currentUser) {

        if (currentUser == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
        if (file.isEmpty()) {
            return ResponseEntity.badRequest().body(Map.of("message", "El archivo no puede estar vacío"));
        }

        String originalFileName = file.getOriginalFilename();
        if (!isPdfFile(file.getContentType(), originalFileName)) {
            return ResponseEntity.badRequest().body(Map.of("message", "Solo se aceptan archivos PDF"));
        }

        try {
            Path uploadPath = Paths.get(UPLOAD_DIR);
            Files.createDirectories(uploadPath);

            String extension = getFileExtension(originalFileName);
            String storedName = currentUser.getId() + "_" + UUID.randomUUID() + extension;
            Path destination = uploadPath.resolve(storedName);
            Files.copy(file.getInputStream(), destination, StandardCopyOption.REPLACE_EXISTING);
            byte[] fileBytes = Files.readAllBytes(destination);

            String documentUrl = appBaseUrl + "/uploads/verifications/" + storedName;
            String normalizedFileName = originalFileName != null ? originalFileName : storedName;

            VerificationRequest request = VerificationRequest.builder()
                    .user(currentUser)
                    .fileName(normalizedFileName)
                    .filePath(destination.toString())
                    .status(VerificationStatus.PENDING)
                    .build();
            verificationRequestRepository.save(request);

            odooService.createVerificationTask(
                    currentUser.getId().toString(),
                    currentUser.getName(),
                    currentUser.getEmail(),
                    normalizedFileName,
                    PDF_MIME_TYPE,
                    fileBytes,
                    documentUrl
            );

            log.info("Documento PDF subido para verificacion: usuario={} archivo={}", currentUser.getEmail(), storedName);

            return ResponseEntity.ok(Map.of(
                    "message", "Documento enviado para revision",
                    "fileName", normalizedFileName,
                    "documentUrl", documentUrl,
                    "status", VerificationStatus.PENDING.name()
            ));
        } catch (IOException e) {
            log.error("Error guardando documento de verificacion: {}", e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(Map.of("message", "Error al guardar el documento"));
        }
    }

    @GetMapping("/status")
    public ResponseEntity<?> getStatus(@AuthenticationPrincipal User currentUser) {
        if (currentUser == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
        return verificationRequestRepository.findTopByUserOrderByCreatedAtDesc(currentUser)
                .map(req -> ResponseEntity.ok(Map.of(
                        "status", req.getStatus().name(),
                        "fileName", req.getFileName(),
                        "createdAt", req.getCreatedAt().toString()
                )))
                .orElse(ResponseEntity.ok(Map.of("status", "NONE")));
    }

    @PostMapping("/approve/{userId}")
    public ResponseEntity<?> approveVerification(@PathVariable UUID userId) {
        User user = userRepository.findById(userId).orElse(null);
        if (user == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(Map.of("message", "Usuario no encontrado"));
        }
        try {
            verificationRequestRepository.findTopByUserOrderByCreatedAtDesc(user).ifPresent(req -> {
                req.setStatus(VerificationStatus.APPROVED);
                verificationRequestRepository.save(req);
            });
            user.setTeacherVerified(true);
            user.setRole(Role.PROFESOR_CERTIFICADO);
            userRepository.save(user);
            try {
                emailService.sendVerificationApprovedEmail(user.getEmail(), user.getName());
            } catch (Exception emailEx) {
                log.warn("No se pudo enviar email de verificación aprobada para {}: {}",
                        user.getEmail(), emailEx.getMessage());
            }
            log.info("Verificación aprobada para usuario={}", user.getEmail());
            return ResponseEntity.ok(Map.of("message", "Verificación aprobada"));
        } catch (Exception e) {
            log.error("Error aprobando verificación para {}: {}", userId, e.getMessage());
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(Map.of("message", "Error al aprobar la verificación"));
        }
    }

    @PostMapping("/odoo-webhook")
    public ResponseEntity<?> odooWebhook(
            @RequestHeader(value = "X-Odoo-Secret", required = false) String receivedSecret,
            @RequestBody Map<String, String> body) {

        if (odooWebhookSecret == null || odooWebhookSecret.isBlank()
                || !odooWebhookSecret.equals(receivedSecret)) {
            log.warn("Odoo webhook: secreto invalido o ausente");
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                    .body(Map.of("message", "Secreto no valido"));
        }

        String userIdStr = body.get("userId");
        String action = body.get("action");

        if (userIdStr == null || action == null) {
            return ResponseEntity.badRequest()
                    .body(Map.of("message", "Se requieren 'userId' y 'action'"));
        }

        UUID userId;
        try {
            userId = UUID.fromString(userIdStr);
        } catch (IllegalArgumentException e) {
            return ResponseEntity.badRequest().body(Map.of("message", "userId invalido"));
        }

        User user = userRepository.findById(userId).orElse(null);
        if (user == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(Map.of("message", "Usuario no encontrado"));
        }

        switch (action.toLowerCase(Locale.ROOT)) {
            case "approve" -> {
                verificationRequestRepository.findTopByUserOrderByCreatedAtDesc(user).ifPresent(req -> {
                    req.setStatus(VerificationStatus.APPROVED);
                    verificationRequestRepository.save(req);
                });
                user.setTeacherVerified(true);
                user.setRole(Role.PROFESOR_CERTIFICADO);
                userRepository.save(user);
                emailService.sendVerificationApprovedEmail(user.getEmail(), user.getName());
                log.info("Odoo webhook: verificacion APROBADA para usuario={}", user.getEmail());
                return ResponseEntity.ok(Map.of("message", "Verificacion aprobada"));
            }
            case "reject" -> {
                verificationRequestRepository.findTopByUserOrderByCreatedAtDesc(user).ifPresent(req -> {
                    req.setStatus(VerificationStatus.REJECTED);
                    verificationRequestRepository.save(req);
                });
                userRepository.save(user);
                emailService.sendVerificationRejectedEmail(user.getEmail(), user.getName());
                log.info("Odoo webhook: verificacion RECHAZADA para usuario={}", user.getEmail());
                return ResponseEntity.ok(Map.of("message", "Verificacion rechazada"));
            }
            default -> {
                return ResponseEntity.badRequest()
                        .body(Map.of("message", "Accion no valida. Usa 'approve' o 'reject'"));
            }
        }
    }

    private boolean isPdfFile(String contentType, String fileName) {
        if (contentType == null || !PDF_MIME_TYPE.equalsIgnoreCase(contentType.trim())) {
            return false;
        }
        String extension = getFileExtension(fileName);
        return ".pdf".equals(extension);
    }

    private String getFileExtension(String fileName) {
        if (fileName == null || fileName.isBlank()) {
            return "";
        }
        String safeName = Path.of(fileName).getFileName().toString();
        int dotIndex = safeName.lastIndexOf('.');
        if (dotIndex < 0) {
            return "";
        }
        return safeName.substring(dotIndex).toLowerCase(Locale.ROOT);
    }
}