package fr.titionfire.ffsaf.utils;

import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.domain.service.VirusScannerService;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.exception.DInternalError;
import fr.titionfire.ffsaf.rest.exception.DetailException;
import io.quarkiverse.antivirus.runtime.AntivirusScanResult;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jodd.net.MimeTypes;
import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.multipart.FileUpload;

import java.io.*;
import java.net.URISyntaxException;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;


public class Utils {
    private static final Logger LOGGER = Logger.getLogger(Utils.class);

    public static String HTML_HEADER = """
            <!DOCTYPE html>
            <html data-lt-installed="true">
            
            <head>
                <meta charset="UTF-8">
                <title>%s</title>
                <style>
                    body {
                        font-family: Arial, sans-serif;
                        line-height: 1.6;
                        color: #333;
                        max-width: 600px;
                        margin: 0 auto;
                        padding: 20px;
                    }
            
                    .header {
                        background-color: #003366;
                        color: white;
                        padding: 20px;
                        text-align: center;
                        border-radius: 5px 5px 0 0;
                    }
            
                    .content {
                        padding: 20px;
                        background-color: #f9f9f9;
                        border-radius: 0 0 5px 5px;
                        border: 1px solid #ddd;
                        border-top: none;
                    }
            
                    .button {
                        display: inline-block;
                        padding: 10px 20px;
                        background-color: #003366;
                        color: white !important;
                        text-decoration: none;
                        border-radius: 5px;
                        margin: 15px 0;
                    }
            
                    .footer {
                        margin-top: 20px;
                        font-size: 0.9em;
                        color: #666;
                        text-align: center;
                    }
            
                    .highlight {
                        font-weight: bold;
                        color: #003366;
                    }
                </style>
            </head>
            
            <body data-gramm="false" data-lt-tmp-id="lt-957854">
                <div class="header">
                    <div><img src="https://intra.ffsaf.fr/Logo-FFSAF-2023.png" alt="ffsaf" height="128">
                        <h1>F&eacute;d&eacute;ration France Soft Armored Fighting</h1>
                    </div>
                </div>
                <div class="content">
                    <p>Bonjour,</p>
            """;
    public static String HTML_FOOTER = """
                <p>Cordialement,<br>L&rsquo;&eacute;quipe de la FFSAF</p>
                </div>
            </body>
            </html>
            """;

    public static int getSaison() {
        return getSaison(new Date());
    }

    public static int getSaison(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);

        if (calendar.get(Calendar.MONTH) >= Calendar.SEPTEMBER) {
            return calendar.get(Calendar.YEAR);
        } else {
            return calendar.get(Calendar.YEAR) - 1;
        }
    }

    public static Calendar toCalendar(Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        return cal;
    }

    public static Categorie getCategoryFormBirthDate(Date birth_date, Date currentDate) {

        int currentSaison = getSaison(currentDate);
        int birthYear = toCalendar(birth_date).get(Calendar.YEAR);

        int diff = currentSaison - birthYear;
        if (diff < 6) {
            return Categorie.SUPER_MINI;
        } else if (diff < 8) {
            return Categorie.MINI_POUSSIN;
        } else if (diff < 10) {
            return Categorie.POUSSIN;
        } else if (diff < 12) {
            return Categorie.BENJAMIN;
        } else if (diff < 14) {
            return Categorie.MINIME;
        } else if (diff < 16) {
            return Categorie.CADET;
        } else if (diff < 18) {
            return Categorie.JUNIOR;
        } else if (diff < 25) {
            return Categorie.SENIOR1;
        } else if (diff < 35) {
            return Categorie.SENIOR2;
        } else if (diff < 45) {
            return Categorie.VETERAN1;
        } else {
            return Categorie.VETERAN2;
        }
    }

    public static Uni<String> moveMedia(long idSrc, long idDest, String media, String dirSrc, String dirDst) {
        return Uni.createFrom().nullItem().map(__ -> {
            File dirFile = new File(media, dirSrc);
            if (!dirFile.exists())
                return "Not found";

            File dirDestFile = new File(media, dirDst);
            if (!dirDestFile.exists())
                if (!dirDestFile.mkdirs())
                    return "Fail to create directory " + dirDestFile;

            FilenameFilter filter = (directory, filename) -> filename.startsWith(idSrc + ".");
            File[] files = dirFile.listFiles(filter);
            if (files == null || files.length == 0)
                return "Not found";

            FilenameFilter filter2 = (directory, filename) -> filename.startsWith(idDest + ".");
            File[] files2 = dirDestFile.listFiles(filter2);
            if (files2 != null) {
                for (File file : files2) {
                    //noinspection ResultOfMethodCallIgnored
                    file.delete();
                }
            }

            for (File file : files) {
                //noinspection ResultOfMethodCallIgnored
                file.renameTo(new File(dirDestFile,
                        file.getName().replaceFirst(String.valueOf(idSrc), String.valueOf(idDest))));
            }

            return "Ok";
        });
    }

    public static Uni<String> uploadFile(VirusScannerService ss, FileUpload file, long id, String media, String dir) {
        if (file == null || file.size() == 0)
            return Uni.createFrom().item("Ok");

        LOGGER.infof("Received file upload request for: %s (size: %d bytes)", file.fileName(), file.size());
        return Uni.createFrom().<InputStream>item(() -> {
                    try {
                        // Read the entire file into memory for virus scanning
                        // ByteArrayInputStream fully supports mark/reset operations required by ClamAV
                        // This ensures we can scan the file content before any filesystem storage
                        InputStream fileStream = java.nio.file.Files.newInputStream(file.uploadedFile());
                        byte[] fileBytes = fileStream.readAllBytes();
                        fileStream.close(); // Close the file stream immediately
                        return new ByteArrayInputStream(fileBytes);
                    } catch (IOException e) {
                        throw new RuntimeException("Failed to read uploaded file: " + e.getMessage(), e);
                    }
                }).runSubscriptionOn(io.smallrye.mutiny.infrastructure.Infrastructure.getDefaultWorkerPool()).onItem()
                .transformToUni(inputStream -> {
                    // Perform virus scanning reactively using the input stream
                    return ss.scanFileReactive(file.fileName(), inputStream).onItem().invoke(() -> {
                        try {
                            inputStream.close();
                        } catch (IOException e) {
                            LOGGER.warn("Warning: Failed to close input stream: " + e.getMessage());
                        }
                    });
                })
                .onItem().transformToUni(scanResults -> Uni.createFrom().item(Unchecked.supplier(() -> {
                    // Check if any scanner found a threat
                    for (AntivirusScanResult result : scanResults) {
                        if (result.getStatus() != Response.Status.OK.getStatusCode()) {
                            LOGGER.warnf("THREAT DETECTED in %s: %s", file.fileName(), result.getMessage());
                            throw new DetailException(Response.Status.fromStatusCode(result.getStatus()),
                                    "THREAT_DETECTED on File " + file.fileName() + " is infected: " + result.getMessage());
                        }
                    }

                    // File is clean - now we can safely process it
                    LOGGER.infof("File is clean: %s (size=%d) (contentType=%s)", file.fileName(), file.size(),
                            file.contentType());

                    String[] detectedExtensions = MimeTypes.findExtensionsByMimeTypes(file.contentType(), false);
                    if (detectedExtensions.length == 0)
                        throw new DBadRequestException(
                                "Fail to detect file extension for MIME type " + file.contentType());

                    File dirFile = new File(media, dir);
                    if (!dirFile.exists())
                        if (!dirFile.mkdirs())
                            throw new DInternalError("Fail to create directory " + dir);

                    FilenameFilter filter = (directory, filename) -> filename.startsWith(id + ".");
                    File[] files = dirFile.listFiles(filter);
                    if (files != null) {
                        for (File f2 : files) {
                            //noinspection ResultOfMethodCallIgnored
                            f2.delete();
                        }
                    }

                    Path f = file.filePath();
                    System.out.println(f + " -> " + new File(dirFile, id + "." + detectedExtensions[0]));
                    Files.copy(f, new File(dirFile, id + "." + detectedExtensions[0]).toPath(),
                            StandardCopyOption.REPLACE_EXISTING);
                    return "ok";
                })));
    }

    public static Uni<Response> getMediaFile(long id, String media, String dirname,
                                             Uni<?> uniBase) throws URISyntaxException {
        return getMediaFile(id, media, dirname, null, uniBase, true);
    }

    public static Uni<Response> getMediaFileNoDefault(long id, String media, String dirname,
                                                      Uni<?> uniBase) throws URISyntaxException {
        return getMediaFile(id, media, dirname, null, uniBase, false);
    }

    public static Uni<Response> getMediaFile(long id, String media, String dirname, String out_filename,
                                             Uni<?> uniBase, boolean default_) throws URISyntaxException {
        Future<Pair<File, byte[]>> future = CompletableFuture.supplyAsync(() -> {
            FilenameFilter filter = (directory, filename) -> filename.startsWith(id + ".");
            File[] files = new File(media, dirname).listFiles(filter);
            if (files != null && files.length > 0) {
                File file = files[0];
                try {
                    byte[] data = Files.readAllBytes(file.toPath());
                    return new Pair<>(file, data);
                } catch (IOException ignored) {
                }
            }
            return null;
        });

        Future<byte[]> future2 = CompletableFuture.supplyAsync(() -> {
            try (InputStream st = Utils.class.getClassLoader().getResourceAsStream("asset/blank-profile-picture.png")) {
                if (st == null)
                    return null;
                return st.readAllBytes();
            } catch (IOException ignored) {
            }
            return null;
        });

        return uniBase.chain(__ -> Uni.createFrom().future(future)
                .chain(filePair -> {
                    if (filePair == null) {
                        if (default_) {
                            return Uni.createFrom().future(future2).map(data -> {
                                if (data == null)
                                    return Response.noContent().build();

                                String mimeType = "image/apng";
                                Response.ResponseBuilder resp = Response.ok(data);
                                resp.type(MediaType.APPLICATION_OCTET_STREAM);
                                resp.header(HttpHeaders.CONTENT_LENGTH, data.length);
                                resp.header(HttpHeaders.CONTENT_TYPE, mimeType);
                                resp.header(HttpHeaders.CONTENT_DISPOSITION,
                                        "inline; " + ((out_filename == null) ? "" : "filename=\"" + out_filename + "\""));
                                return resp.build();
                            });
                        } else {
                            Response.ResponseBuilder resp = Response.status(404);
                            resp.header(HttpHeaders.CACHE_CONTROL, "max-age=600");
                            return Uni.createFrom().item(resp.build());
                        }
                    } else {
                        return Uni.createFrom().item(() -> {
                            String mimeType = URLConnection.guessContentTypeFromName(filePair.getKey().getName());


                            String filename = out_filename;
                            if (filename != null && (filename.length() < 5 || filename.lastIndexOf(
                                    '.') <= filename.length() - 6)) {
                                filename += filePair.getKey().getName()
                                        .substring(filePair.getKey().getName().lastIndexOf('.'));
                            }

                            Response.ResponseBuilder resp = Response.ok(filePair.getValue());
                            resp.type(MediaType.APPLICATION_OCTET_STREAM);
                            resp.header(HttpHeaders.CONTENT_LENGTH, filePair.getValue().length);
                            resp.header(HttpHeaders.CONTENT_TYPE, mimeType);
                            resp.header(HttpHeaders.CONTENT_DISPOSITION,
                                    "inline; " + ((filename == null) ? "" : "filename=\"" + filename + "\""));
                            return resp.build();
                        });
                    }
                }));
    }

    public static Uni<?> deleteMedia(long id, String media, String dir) {
        return Uni.createFrom().nullItem().map(__ -> {
            File dirFile = new File(media, dir);
            if (!dirFile.exists())
                return "OK";

            FilenameFilter filter = (directory, filename) -> filename.startsWith(id + ".");
            File[] files = dirFile.listFiles(filter);
            if (files != null) {
                for (File file : files) {
                    //noinspection ResultOfMethodCallIgnored
                    file.delete();
                }
            }
            return "Ok";
        });
    }

    public static int getDaysBeforeCompetition(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);

        Calendar now = Calendar.getInstance();
        now.set(Calendar.HOUR_OF_DAY, 0);
        now.set(Calendar.MINUTE, 0);
        now.set(Calendar.SECOND, 0);

        return (int) ((calendar.getTimeInMillis() - now.getTimeInMillis()) / (1000 * 60 * 60 * 24));
    }

    public static String formatPrenom(String input) {
        if (input == null || input.isEmpty()) {
            return input;
        }

        StringBuilder result = new StringBuilder();
        String[] mots = input.split(" ");

        for (String mot : mots) {
            if (!mot.isEmpty()) {
                String[] parties = mot.split("[-']");
                StringBuilder motFormate = new StringBuilder();

                for (int i = 0; i < parties.length; i++) {
                    if (!parties[i].isEmpty()) {
                        String premiereLettre = parties[i].substring(0, 1).toUpperCase();
                        String reste = parties[i].substring(1).toLowerCase();
                        motFormate.append(premiereLettre).append(reste);
                    }

                    if (i < parties.length - 1) {
                        motFormate.append(mot.charAt(mot.indexOf(parties[i]) + parties[i].length()));
                    }
                }
                result.append(motFormate).append(" ");
            }
        }
        return result.toString().trim();
    }

    public static String getFullName(Object... models) {
        for (Object model : models) {
            if (model == null)
                continue;
            if (model instanceof MembreModel membreModel)
                return membreModel.getFname() + " " + membreModel.getLname();
            if (model instanceof CompetitionGuestModel guestModel)
                return guestModel.getFname() + " " + guestModel.getLname();
        }
        return "";
    }
}
