package dev.lions.unionflow.server.service;

import jakarta.enterprise.context.ApplicationScoped;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.config.inject.ConfigProperty;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.UUID;

/**
 * Service de stockage de fichiers sur le système de fichiers.
 * Gère l'upload, le stockage, et le calcul des hash.
 *
 * @author UnionFlow Team
 * @version 1.0
 * @since 2026-03-15
 */
@Slf4j
@ApplicationScoped
public class FileStorageService {

    // Taille max: 5 MB
    private static final long MAX_FILE_SIZE = 5 * 1024 * 1024;

    // Types MIME autorisés
    private static final String[] ALLOWED_MIME_TYPES = {
        "image/jpeg",
        "image/jpg",
        "image/png",
        "image/gif",
        "application/pdf"
    };

    @ConfigProperty(name = "unionflow.upload.directory", defaultValue = "./uploads")
    String uploadDirectory;

    /**
     * Stocke un fichier uploadé et retourne les métadonnées.
     *
     * @param inputStream Flux du fichier
     * @param fileName Nom du fichier original
     * @param mimeType Type MIME
     * @param fileSize Taille du fichier
     * @return Métadonnées du fichier stocké
     * @throws IOException Si erreur d'I/O
     * @throws IllegalArgumentException Si validation échoue
     */
    public FileMetadata storeFile(InputStream inputStream, String fileName, String mimeType, long fileSize)
            throws IOException, java.security.NoSuchAlgorithmException {

        // Validation de la taille
        if (fileSize > MAX_FILE_SIZE) {
            throw new IllegalArgumentException(
                String.format("Fichier trop volumineux. Taille max: %d MB", MAX_FILE_SIZE / (1024 * 1024))
            );
        }

        // Validation du type MIME
        if (!isAllowedMimeType(mimeType)) {
            throw new IllegalArgumentException(
                "Type de fichier non autorisé. Types acceptés: JPEG, PNG, GIF, PDF"
            );
        }

        // Générer un nom unique pour le fichier
        String uniqueFileName = generateUniqueFileName(fileName);

        // Créer le chemin de stockage (organisé par date: uploads/2026/03/15/)
        String relativePath = getRelativePath(uniqueFileName);
        Path targetPath = Paths.get(uploadDirectory, relativePath);

        // Créer les répertoires si nécessaire
        Files.createDirectories(targetPath.getParent());

        // Écrire le fichier et calculer les hash simultanément
        // MD5 et SHA-256 sont des algorithmes obligatoires depuis Java 1.4 (JCA standard)
        // et ne peuvent jamais lever NoSuchAlgorithmException dans un JRE conforme.
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        MessageDigest sha256 = MessageDigest.getInstance("SHA-256");

        byte[] buffer = new byte[8192];
        int bytesRead;
        long totalBytes = 0;

        try (FileOutputStream fos = new FileOutputStream(targetPath.toFile())) {
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
                totalBytes += bytesRead;
                md5.update(buffer, 0, bytesRead);
                sha256.update(buffer, 0, bytesRead);
            }
        }

        // Générer les hash
        String hashMd5 = bytesToHex(md5.digest());
        String hashSha256 = bytesToHex(sha256.digest());

        log.info("Fichier stocké : {} ({} octets) - MD5: {}", uniqueFileName, totalBytes, hashMd5);

        return FileMetadata.builder()
                .nomFichier(uniqueFileName)
                .nomOriginal(fileName)
                .cheminStockage(relativePath)
                .typeMime(mimeType)
                .tailleOctets(totalBytes)
                .hashMd5(hashMd5)
                .hashSha256(hashSha256)
                .build();
    }

    /**
     * Vérifie si le type MIME est autorisé
     */
    private boolean isAllowedMimeType(String mimeType) {
        if (mimeType == null) {
            return false;
        }
        for (String allowed : ALLOWED_MIME_TYPES) {
            if (allowed.equalsIgnoreCase(mimeType)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Génère un nom unique pour le fichier
     */
    private String generateUniqueFileName(String originalFileName) {
        String extension = "";
        int lastDot = originalFileName.lastIndexOf('.');
        if (lastDot > 0) {
            extension = originalFileName.substring(lastDot);
        }
        return UUID.randomUUID().toString() + extension;
    }

    /**
     * Retourne le chemin relatif organisé par date (YYYY/MM/DD/)
     */
    private String getRelativePath(String fileName) {
        LocalDate today = LocalDate.now();
        return String.format("%04d/%02d/%02d/%s",
                today.getYear(),
                today.getMonthValue(),
                today.getDayOfMonth(),
                fileName
        );
    }

    /**
     * Convertit un tableau de bytes en hexadécimal
     */
    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    /**
     * Classe pour encapsuler les métadonnées d'un fichier stocké
     */
    @lombok.Data
    @lombok.Builder
    @lombok.NoArgsConstructor
    @lombok.AllArgsConstructor
    public static class FileMetadata {
        private String nomFichier;
        private String nomOriginal;
        private String cheminStockage;
        private String typeMime;
        private Long tailleOctets;
        private String hashMd5;
        private String hashSha256;
    }
}
