package fr.titionfire.ffsaf.domain.service;

import fr.titionfire.ffsaf.data.model.ClubModel;
import fr.titionfire.ffsaf.data.model.LicenceModel;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.data.repository.ClubRepository;
import fr.titionfire.ffsaf.data.repository.CombRepository;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import fr.titionfire.ffsaf.utils.Utils;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.hibernate.reactive.mutiny.Mutiny;
import org.jboss.logging.Logger;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@WithSession
@ApplicationScoped
public class PDFService {
    private static final Logger LOGGER = Logger.getLogger(PDFService.class);

    @Inject
    CombRepository combRepository;

    @Inject
    ClubRepository clubRepository;

    @ConfigProperty(name = "upload_dir")
    String media;

    @ConfigProperty(name = "pdf-maker.jar-path")
    String pdfMakerJarPath;

    @ConfigProperty(name = "pdf-maker.sign-file")
    String sign_file;

    @Inject
    TradService trad;


    public Uni<Response> getLicencePdf(String subject) {
        return getLicencePdf(combRepository.find("userId = ?1", subject).firstResult()
                .call(m -> Mutiny.fetch(m.getLicences())));
    }

    public Uni<Response> getLicencePdf(Uni<MembreModel> uniBase) {
        return uniBase
                .map(Unchecked.function(m -> {
                    LicenceModel licence = m.getLicences().stream()
                            .filter(licenceModel -> licenceModel.getSaison() == Utils.getSaison() && licenceModel.isValidate())
                            .findFirst()
                            .orElseThrow(() -> new DNotFoundException(trad.t("pas.de.licence.pour.la.saison.en.cours")));

                    try {
                        byte[] buff = make_pdf(m, licence);
                        if (buff == null)
                            throw new IOException("Error making pdf");

                        String mimeType = "application/pdf";

                        Response.ResponseBuilder resp = Response.ok(buff);
                        resp.type(MediaType.APPLICATION_OCTET_STREAM);
                        resp.header(HttpHeaders.CONTENT_LENGTH, buff.length);
                        resp.header(HttpHeaders.CONTENT_TYPE, mimeType);
                        resp.header(HttpHeaders.CONTENT_DISPOSITION,
                                "inline; " + "filename=\"Attestation d'adhésion " + Utils.getSaison() + "-" +
                                        (Utils.getSaison() + 1) + " de " + m.getLname() + " " + m.getFname() + ".pdf\"");
                        return resp.build();
                    } catch (Exception e) {
                        throw new IOException(e);
                    }
                }));
    }

    private byte[] make_pdf(MembreModel m, LicenceModel licence) throws IOException, InterruptedException {
        List<String> cmd = new ArrayList<>();
        cmd.add("java");
        cmd.add("-jar");
        cmd.add(pdfMakerJarPath);

        UUID uuid = UUID.randomUUID();

        cmd.add("/tmp/" + uuid + ".pdf");
        cmd.add("membre");
        cmd.add(m.getFname());
        cmd.add(m.getLname());
        cmd.add(m.getGenre().str);
        cmd.add(m.getCategorie().getName());
        cmd.add(licence.getCertificate() == null ? "" : licence.getCertificate());
        cmd.add(Utils.getSaison() + "");
        cmd.add(m.getLicence() + "");
        cmd.add(m.getClub().getName());
        cmd.add(m.getClub().getNo_affiliation() + "");
        cmd.add(m.getBirth_date() == null ? "--" : new SimpleDateFormat("dd/MM/yyyy").format(m.getBirth_date()));

        FilenameFilter filter = (directory, filename) -> filename.startsWith(m.getId() + ".");
        File[] files = new File(media, "ppMembre").listFiles(filter);
        if (files != null && files.length > 0) {
            File file = files[0];
            cmd.add(file.getAbsolutePath());
        } else {
            cmd.add("/dev/null");
        }

        return getPdf(cmd, uuid);
    }

    public Uni<Response> getAffiliationPdf(String subject) {
        return getAffiliationPdf(
                combRepository.find("userId = ?1", subject).firstResult()
                        .invoke(Unchecked.consumer(m -> {
                            if (m == null || m.getClub() == null)
                                throw new DNotFoundException(trad.t("club.non.trouve"));
                        }))
                        .map(MembreModel::getClub)
                        .call(m -> Mutiny.fetch(m.getAffiliations())));
    }

    public Uni<Response> getAffiliationPdf(long id) {
        return getAffiliationPdf(
                clubRepository.findById(id)
                        .invoke(Unchecked.consumer(m -> {
                            if (m == null)
                                throw new DNotFoundException(trad.t("club.non.trouve"));
                        }))
                        .call(m -> Mutiny.fetch(m.getAffiliations())));
    }


    private Uni<Response> getAffiliationPdf(Uni<ClubModel> uniBase) {
        return uniBase
                .map(Unchecked.function(m -> {
                    if (m.getAffiliations().stream()
                            .noneMatch(licenceModel -> licenceModel.getSaison() == Utils.getSaison()))
                        throw new DNotFoundException(trad.t("pas.d.affiliation.pour.la.saison.en.cours"));

                    try {
                        byte[] buff = make_pdf(m);
                        if (buff == null)
                            throw new IOException("Error making pdf");

                        String mimeType = "application/pdf";

                        Response.ResponseBuilder resp = Response.ok(buff);
                        resp.type(MediaType.APPLICATION_OCTET_STREAM);
                        resp.header(HttpHeaders.CONTENT_LENGTH, buff.length);
                        resp.header(HttpHeaders.CONTENT_TYPE, mimeType);
                        resp.header(HttpHeaders.CONTENT_DISPOSITION,
                                "inline; " + "filename=\"Attestation d'affiliation " + Utils.getSaison() + "-" +
                                        (Utils.getSaison() + 1) + " de " + m.getName() + ".pdf\"");
                        return resp.build();
                    } catch (Exception e) {
                        throw new IOException(e);
                    }
                }));
    }

    private byte[] make_pdf(ClubModel m) throws IOException, InterruptedException {
        List<String> cmd = new ArrayList<>();
        cmd.add("java");
        cmd.add("-jar");
        cmd.add(pdfMakerJarPath);

        UUID uuid = UUID.randomUUID();

        cmd.add("/tmp/" + uuid + ".pdf");
        cmd.add("club");
        cmd.add(m.getName());
        cmd.add(Utils.getSaison() + "");
        cmd.add(m.getNo_affiliation() + "");
        cmd.add(new File(sign_file).getAbsolutePath());

        return getPdf(cmd, uuid);
    }

    static byte[] getPdf(List<String> cmd, UUID uuid) throws IOException, InterruptedException {
        ProcessBuilder processBuilder = new ProcessBuilder(cmd);
        processBuilder.redirectErrorStream(true);
        Process process = processBuilder.start();

        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));

        StringBuilder builder = new StringBuilder();
        Thread t = new Thread(() -> {
            try {
                String line;
                while ((line = reader.readLine()) != null)
                    builder.append(line).append("\n");
            } catch (Exception ignored) {
            }
        });
        t.start();

        int code = -1;
        if (!process.waitFor(30, TimeUnit.SECONDS)) {
            process.destroy();
            builder.append("Timeout...");
        } else {
            code = process.exitValue();
        }

        if (t.isAlive())
            t.interrupt();

        PDFService.LOGGER.debug("PDF maker: " + builder);

        if (code != 0) {
            throw new IOException("Error code: " + code);
        } else {
            File file = new File("/tmp/" + uuid + ".pdf");
            try (FileInputStream fis = new FileInputStream(file)) {
                byte[] buff = fis.readAllBytes();
                //noinspection ResultOfMethodCallIgnored
                file.delete();
                return buff;
            } catch (IOException e) {
                //noinspection ResultOfMethodCallIgnored
                file.delete();
            }
            return null;
        }
    }
}
