package org.gcube.application.cms.usecases;

import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.gcube.application.cms.custom.gna.concessioni.model.ProfiledConcessione;
import org.gcube.application.cms.serialization.Serialization;
import org.gcube.application.cms.tests.TokenSetter;
import org.gcube.application.cms.usecases.legacyConcessioni.ConcessioniManagementUtils;
import org.gcube.application.geoportal.common.model.configuration.Configuration;
import org.gcube.application.geoportal.common.model.document.Project;
import org.gcube.application.geoportal.common.model.document.access.Access;
import org.gcube.application.geoportal.common.model.document.access.AccessPolicy;
import org.gcube.application.geoportal.common.model.document.lifecycle.LifecycleInformation;
import org.gcube.application.geoportal.common.model.legacy.*;
import org.gcube.application.geoportal.common.model.rest.CreateRelationshipRequest;
import org.gcube.application.geoportal.common.model.rest.QueryRequest;
import org.gcube.application.geoportal.common.model.rest.StepExecutionRequest;
import org.gcube.application.geoportal.common.model.rest.TempFile;
import org.gcube.application.geoportal.common.model.useCaseDescriptor.Field;
import org.gcube.application.geoportal.common.rest.Projects;
import org.gcube.application.geoportal.common.utils.FileSets;
import org.gcube.application.geoportal.common.utils.Files;
import org.gcube.application.geoportal.common.utils.tests.GCubeTest;

import java.io.File;
import java.io.IOException;
import java.rmi.RemoteException;
import java.sql.SQLOutput;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.stream.Collectors;

import static org.gcube.application.geoportal.client.plugins.GeoportalAbstractPlugin.projects;

@Slf4j
public class ExportConcessioniAsProjects {






    public static void main(String[] args) throws InterruptedException, IOException {

        // read from imported folder
         File dir= new File("/Users/fabioisti/git/gcube-cms-suite/import1669889538860");

         // select only cluster
        dir = new File(dir,"61d5ec7495184b186ad0c2e4");



         File errorDir=new File(dir,"errors_"+ Instant.now().toString());

         Boolean pushProjects=true;

       // File dir= new File("test-data/test-packages/related_concessioni");

        // File dir = new File("/Users/fabioisti/Documents/Work/GNA DATA/Bug_23378");
        //String targetContext="/gcube/devsec/devVRE";
       // String targetContext="/pred4s/preprod/preVRE";

        Projects<Project> client=projects("concessioni-estere").build();;




        Integer totalCount = 0;


        // Title - > Time ordered list
        ConcessioniManagementUtils.ImportFolder importFolder = ConcessioniManagementUtils.loadImportFolder(dir);

        Files.saveString(importFolder.sort(),new File(dir,"relationships.csv").toPath());

        TokenSetter.set(GCubeTest.getContext());



        if(!pushProjects){
            System.out.println("PUSH PROJECTS FLAG IS FALSE.. CLOSING.");
            StringBuilder reportBuilder = new StringBuilder();
            StringBuilder concReportBuilder = new StringBuilder();

            importFolder.getRelationshipMap().forEach((s,l)->{
                l.forEach(c->concReportBuilder.append(c.getReport().getStatus()+","+c.getReport().getWarningMessages()+","+c.getMongo_id()+","+c.getNome()+"\n"));
                try {
                  //  reportBuilder.append(getExisting(l.get(0),client).size()+","+s+"\n");
                } catch (Throwable e) {
                    log.error("Unable to get count for {} ",s,e);
                    throw new RuntimeException(e);
                }
            });
            Files.saveString(reportBuilder.toString(),new File(dir,"found.csv").toPath());
            Files.saveString(concReportBuilder.toString(),new File(dir,"originalStatuses.csv").toPath());
            System.exit(0);
        }


        // StorageUtils storage = new StorageUtils();

        AtomicLong count = new AtomicLong(0);
        AtomicLong warnCount = new AtomicLong(0);
        AtomicLong errCount = new AtomicLong(0);

        ExecutorService service = Executors.newFixedThreadPool(3);

        long startProcess = System.currentTimeMillis();

        importFolder.getRelationshipMap().forEach((s,l)->{

             AtomicReference<Boolean> publish = new AtomicReference<>(true);
            AtomicReference<Boolean> delete = new AtomicReference<>(false);
            // Check if exists
            Concessione c = l.get(0);
            try {
                List<Project> existing= getExisting(c,client);
                if(existing.isEmpty()) log.info("Not Found {}",c.getNome());
                else {
                    // found projects, check status
                    existing.forEach(project -> {
                        if (!project.getLifecycleInformation().getLastOperationStatus().equals(LifecycleInformation.Status.OK))
                            delete.set(true);
                    });
                    if(delete.get()){
                        log.debug("Deleting error set for {}",c.getNome());
                        for (Project project : existing) {
                            client.deleteById(project.getId(),true);
                        }
                    }else publish.set(false);
                }
            }catch(NullPointerException e){}
            catch (Throwable t){throw new RuntimeException("Unexpected Exception while checking for "+c.getNome());}

            log.info("Project name {} publish : {}",s,publish);
            if(publish.get()) {
                String relationshipTarget = null;
                for (Concessione concessione : l) {

                    String finalRelationshipTarget = relationshipTarget;
                    CompletableFuture<String> lastPublished = CompletableFuture.supplyAsync(new Supplier<String>() {
                        @Override
                        public String get() {
                            return publish(concessione, client, errCount, count, finalRelationshipTarget, errorDir);
                        }
                    }, service);
                    try {
                        relationshipTarget = lastPublished.get();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    } catch (ExecutionException e) {
                        throw new RuntimeException(e);
                    }

                }
            }
        });



        while (!service.awaitTermination(1, TimeUnit.MINUTES)) {
            log.info("Waiting .. completed {}, out of {} ",count.get(),importFolder.getLoadedCount());
            if(importFolder.getLoadedCount().get()==count.get()) service.shutdown();
        }

        System.out.println("Completed "+count.get()+" [elapsedTime = "+(System.currentTimeMillis()-startProcess)/1000+" sec] [warn : "+warnCount.get()+", err : "+errCount.get()+"]");

        Configuration conf = client.getConfiguration();
        conf.getArchives().stream().forEach(archive -> {
            if (archive.getType().equals("DOCUMENT-STORE-COLLECTION")){
                archive.forEach((s, o) ->{

                    System.out.println(s+"\t:\t"+o);
                });
            }
        });

    }






    private static String publish(Concessione c, Projects<Project> client, AtomicLong errCount, AtomicLong count, String relationshipTarget, File errorDir){
        Project project = null;
        try {
            long startTime = System.currentTimeMillis();
            FileSets.RequestBuilder req=null;

            log.info("Using {} {}",c.getNome(),c.getMongo_id());



            //Copy core fields

            Document doc=new Document();
            doc.put(ProfiledConcessione.NOME,c.getNome());
            doc.put(ProfiledConcessione.INTRODUZIONE,c.getIntroduzione());
            doc.put(ProfiledConcessione.DESCRIZIONE_CONTENUTO,c.getDescrizioneContenuto());
            doc.put(ProfiledConcessione.AUTHORS,c.getAuthors());
            doc.put(ProfiledConcessione.CONTRIBUTORE,c.getContributore());
            doc.put(ProfiledConcessione.TITOLARI,c.getTitolari());
            doc.put(ProfiledConcessione.RESPONSABILE,c.getResponsabile());
            doc.put(ProfiledConcessione.EDITORS,c.getEditore());
            doc.put(ProfiledConcessione.FONTI_FINANZIAMENTO,c.getFontiFinanziamento());
            doc.put(ProfiledConcessione.SOGGETTO,c.getSoggetto());
            doc.put(ProfiledConcessione.RISORSE_CORRELATE,c.getRisorseCorrelate());
            doc.put(ProfiledConcessione.DATA_INZIO_PROGETTO,c.getDataInizioProgetto());
            doc.put(ProfiledConcessione.DATA_FINE_PROGETTO,c.getDataFineProgetto());
            doc.put(ProfiledConcessione.TITOLARE_LICENZA,c.getTitolareLicenza());
            doc.put(ProfiledConcessione.TITOLARE_COPYRIGHT,c.getTitolareCopyright());
            doc.put(ProfiledConcessione.PAROLE_CHIAVE_LIBERE,c.getParoleChiaveLibere());
            doc.put(ProfiledConcessione.PAREOLE_CHIAVE_ICCD,c.getParoleChiaveICCD());

            // CREATE Project

            project = client.createNew(doc);

            // set Access
            Access toSetAccess = new Access();
            toSetAccess.setPolicy(AccessPolicy.valueOf(c.getPolicy().toString()));
            toSetAccess.setLicense(c.getLicenzaID());
            project = client.setAccessPolicy(project.getId(),toSetAccess);


            // Relazione
            if(c.getRelazioneScavo()!=null) {
                RelazioneScavo rel=c.getRelazioneScavo();
                Document relDoc= new Document();
                relDoc.put(ProfiledConcessione.Sections.TITOLO,rel.getTitolo());
                relDoc.put(ProfiledConcessione.Relazione.RESPONSABILI,rel.getResponsabili());
                relDoc.put(ProfiledConcessione.SOGGETTO,rel.getSoggetto());

                Document updated =project.getTheDocument();
                updated.put(ProfiledConcessione.RELAZIONE_SCAVO,relDoc);
                project = client.updateDocument(project.getId(),updated);

                List<PersistedContent> content=rel.getActualContent();
                if(content!=null && !content.isEmpty()) {
                    req = FileSets.build("$." + ProfiledConcessione.RELAZIONE_SCAVO, "fileset",
                            "$." + ProfiledConcessione.RELAZIONE_SCAVO + "." + Field.CHILDREN
                                    + "[?(@.fileset)]");
                    req.addAll(convertFiles(content));
                    req.setAccess(getAccess(rel));



                    project = client.registerFileSet(project.getId(),req.getTheRequest());
                }
                // upload fileset
            }


            if(c.getAbstractRelazione()!=null) {
                AbstractRelazione abs=c.getAbstractRelazione();
                Document absDoc= new Document();
                absDoc.put(ProfiledConcessione.Sections.TITOLO,abs.getTitolo());
                absDoc.put(ProfiledConcessione.AbstractRelazione.ABSTRACT_ENG,abs.getAbstractEng());
                absDoc.put(ProfiledConcessione.AbstractRelazione.ABSTRACT_ITA,abs.getAbstractIta());


                Document updated =project.getTheDocument();
                updated.put(ProfiledConcessione.ABSTRACT_RELAZIONE,absDoc);
                project = client.updateDocument(project.getId(),updated);

                //TODO ask Fra about mapping
                // list persited content -> absIta, absEng

//                    List<PersistedContent> content=abs.getActualContent();
//                    if(content!=null && !content.isEmpty()) {
//                        req = FileSets.build("$." + ProfiledConcessione.RELAZIONE_SCAVO, "fileset",
//                                "$." + ProfiledConcessione.RELAZIONE_SCAVO + "." + Field.CHILDREN
//                                        + "[?(@.fileset)]");
//                        content.forEach(uploader);
//                        project = client.registerFileSet(project.getId(),req.getTheRequest());
//                    }

            }

            //Immagini rappresentative
            if(c.getImmaginiRappresentative()!=null){
                List<Document> imgs=new ArrayList<>();
                for (int i = 0; i < c.getImmaginiRappresentative().size(); i++) {
                    UploadedImage img=c.getImmaginiRappresentative().get(i);
                    Document imgDoc=new Document();
                    imgDoc.put(ProfiledConcessione.Sections.TITOLO,img.getTitolo());
                    imgDoc.put(ProfiledConcessione.Immagini.DIDASCALIA,img.getDidascalia());
                    imgDoc.put(ProfiledConcessione.Relazione.RESPONSABILI,img.getResponsabili());
                    imgDoc.put(ProfiledConcessione.SOGGETTO,img.getSoggetto());
                    imgs.add(imgDoc);
                }

                Document updated =project.getTheDocument();
                updated.put(ProfiledConcessione.IMMAGINI_RAPPRESENTATIVE,imgs);
                project = client.updateDocument(project.getId(),updated);

                // FileSets
                for (int i = 0; i < c.getImmaginiRappresentative().size(); i++) {
                    UploadedImage img=c.getImmaginiRappresentative().get(i);
                    List<PersistedContent> content=img.getActualContent();
                    if(content!=null && !content.isEmpty()) {
                        req = FileSets.build("$." + ProfiledConcessione.IMMAGINI_RAPPRESENTATIVE+"["+i+"]", "fileset",
                                "$." + ProfiledConcessione.IMMAGINI_RAPPRESENTATIVE + "." + Field.CHILDREN
                                        + "[?(@.fileset)]");
                        req.addAll(convertFiles(content));
                        req.setAccess(getAccess(img));
                        project = client.registerFileSet(project.getId(),req.getTheRequest());
                    }
                }
            }

            // POS
            if(c.getPosizionamentoScavo()!=null) {
                LayerConcessione l=c.getPosizionamentoScavo();
                Document lDoc= new Document();
                lDoc.put(ProfiledConcessione.Sections.TITOLO,l.getTitolo());
                lDoc.put(ProfiledConcessione.RESPONSABILE,l.getResponsabile());
                lDoc.put(ProfiledConcessione.AUTHORS,l.getAuthors());
                lDoc.put(ProfiledConcessione.PAREOLE_CHIAVE_ICCD,l.getParoleChiaveICCD());
                lDoc.put(ProfiledConcessione.PAROLE_CHIAVE_LIBERE,l.getParoleChiaveLibere());
                lDoc.put(ProfiledConcessione.Layers.TOPIC,l.getTopicCategory());
                lDoc.put(ProfiledConcessione.Layers.SUB_TOPIC,l.getSubTopic());
                lDoc.put(ProfiledConcessione.Layers.METODO_RACCOLTA,l.getMetodoRaccoltaDati());
                lDoc.put(ProfiledConcessione.Layers.SCALA_ACQUISIZIONE,l.getScalaAcquisizione());
                lDoc.put(ProfiledConcessione.Layers.VALUTAZIONE_QUALITA,l.getValutazioneQualita());
                lDoc.put(ProfiledConcessione.Sections.ABSTRACT,l.getAbstractSection());

                Document updated =project.getTheDocument();
                updated.put(ProfiledConcessione.POSIZIONAMENTO_SCAVO,lDoc);
                project = client.updateDocument(project.getId(),updated);

                List<PersistedContent> content=l.getActualContent();
                if(content!=null && !content.isEmpty()) {
                    req = FileSets.build("$." + ProfiledConcessione.POSIZIONAMENTO_SCAVO, "fileset",
                            "$." + ProfiledConcessione.POSIZIONAMENTO_SCAVO + "." + Field.CHILDREN
                                    + "[?(@.fileset)]");
                    req.addAll(convertFiles(content));
                    req.setAccess(getAccess(l));
                    project = client.registerFileSet(project.getId(),req.getTheRequest());
                }
                // upload fileset
            }

            // Piante
            if(c.getPianteFineScavo()!=null){
                List<Document> piante=new ArrayList<>();
                for (int i = 0; i < c.getPianteFineScavo().size(); i++) {
                    LayerConcessione l=c.getPianteFineScavo().get(i);
                    Document lDoc= new Document();
                    lDoc.put(ProfiledConcessione.Sections.TITOLO,l.getTitolo());
                    lDoc.put(ProfiledConcessione.RESPONSABILE,l.getResponsabile());
                    lDoc.put(ProfiledConcessione.AUTHORS,l.getAuthors());
                    lDoc.put(ProfiledConcessione.PAREOLE_CHIAVE_ICCD,l.getParoleChiaveICCD());
                    lDoc.put(ProfiledConcessione.PAROLE_CHIAVE_LIBERE,l.getParoleChiaveLibere());
                    lDoc.put(ProfiledConcessione.Layers.TOPIC,l.getTopicCategory());
                    lDoc.put(ProfiledConcessione.Layers.SUB_TOPIC,l.getSubTopic());
                    lDoc.put(ProfiledConcessione.Layers.METODO_RACCOLTA,l.getMetodoRaccoltaDati());
                    lDoc.put(ProfiledConcessione.Layers.SCALA_ACQUISIZIONE,l.getScalaAcquisizione());
                    lDoc.put(ProfiledConcessione.Layers.VALUTAZIONE_QUALITA,l.getValutazioneQualita());
                    lDoc.put(ProfiledConcessione.Sections.ABSTRACT,l.getAbstractSection());
                    piante.add(lDoc);
                }

                Document updated =project.getTheDocument();
                updated.put(ProfiledConcessione.PIANTE_FINE_SCAVO,piante);
                project = client.updateDocument(project.getId(),updated);

                // FileSets
                for (int i = 0; i < c.getPianteFineScavo().size(); i++) {
                    LayerConcessione layerConcessione=c.getPianteFineScavo().get(i);
                    List<PersistedContent> content=layerConcessione.getActualContent();
                    if(content!=null && !content.isEmpty()) {
                        req = FileSets.build("$." + ProfiledConcessione.PIANTE_FINE_SCAVO+"["+i+"]", "fileset",
                                "$." + ProfiledConcessione.PIANTE_FINE_SCAVO + "." + Field.CHILDREN
                                        + "[?(@.fileset)]");
                        req.addAll(convertFiles(content));
                        req.setAccess(getAccess(layerConcessione));
                        project = client.registerFileSet(project.getId(),req.getTheRequest());
                    }
                }
            }


            // Relationship
            if(relationshipTarget!=null){
                CreateRelationshipRequest relReq = new CreateRelationshipRequest();
                relReq.setProjectId(project.getId());
                relReq.setRelationshipId("follows");
                relReq.setTargetId(relationshipTarget);
                client.setRelation(relReq);
            }


            // Submit for review
            project = client.performStep(project.getId(),step("SUBMIT-FOR-REVIEW"));

            //
            if(project.getLifecycleInformation().getLastOperationStatus().equals(LifecycleInformation.Status.OK))
                project = client.performStep(project.getId(), step("APPROVE-SUBMITTED"));


            System.out.println("Done "+c.getId()+" in "+(System.currentTimeMillis()-startTime)/1000+" sec");

        } catch (Throwable throwable) {
            System.err.println(throwable);
            errCount.incrementAndGet();
            String ser = null;
            File destFile=null;
            //if(relationshipTarget!=null) Files.saveString();
            if(project!=null) {
                ser = org.gcube.application.cms.serialization.Serialization.write(project);
                destFile = new File(errorDir,"project_"+ project.getId());
            }else{
                ser = org.gcube.application.cms.serialization.Serialization.write(c);
                destFile = new File(errorDir,"conc_"+ c.getMongo_id());
            }
            Files.saveString(ser, destFile.toPath());
        } finally {
            count.incrementAndGet();
            return project!=null? project.getId() : null;
        }
    }


    private static final List<TempFile> convertFiles(List<PersistedContent> content){
        return content.stream().
                filter(p -> p instanceof WorkspaceContent)
                .map(p -> new TempFile(null, ((WorkspaceContent) p).getLink(), ((WorkspaceContent) p).getName()))
                .collect(Collectors.toList());
    }

    private static final Access getAccess(AssociatedContent content){
        Access toReturn = new Access();
        toReturn.setLicense(content.getLicenseID());
        toReturn.setPolicy(AccessPolicy.valueOf(content.getPolicy().toString()));
        return toReturn;
    }

    private static final StepExecutionRequest step(String stepID){
        StepExecutionRequest toReturn = new StepExecutionRequest();
        toReturn.setStepID(stepID);
        return toReturn;
    }


    private static List<Project> getExisting(Concessione c,Projects client) throws RemoteException, JsonProcessingException, NullPointerException {
        try {
            QueryRequest req = new QueryRequest();
            String queryString = String.format("{\"_theDocument.nome\" :{\"$eq\" : %1$s}}", quote(c.getNome()));
            log.debug("Query String is {}", queryString);
            req.setFilter(Document.parse(queryString));
            AtomicInteger count = new AtomicInteger(0);
            StringBuilder msg = new StringBuilder();
            ArrayList<Project> toReturn = new ArrayList<>();

            client.query(req).forEachRemaining(p -> {
                Project proj = (Project) p;
                count.incrementAndGet();
                toReturn.add(proj);
                msg.append(proj.getId() + ",");
            });
            log.debug("Found {} for {}", count.get(), c.getNome());
            return toReturn;
        }catch(Throwable t){
            log.warn("ERROR while querying ",t);
            throw t;
        }
    }


    public static String quote(String string) {
        if(string!=null)
           // return "\""+string.replaceAll("\"","\"")+"\"";
            return "\""+string.replaceAll("\"",Matcher.quoteReplacement("\\\""))+"\"";
        else return "\"\"";
//        if (string == null || string.length() == 0) {
//            return "\"\"";
//        }
//
//        char         c = 0;
//        int          i;
//        int          len = string.length();
//        StringBuilder sb = new StringBuilder(len + 4);
//        String       t;
//
//        sb.append('"');
//        for (i = 0; i < len; i += 1) {
//            c = string.charAt(i);
//            switch (c) {
//                case '\\':
//                case '"':
//                    sb.append('\\');
//                    sb.append(c);
//                    break;
//                case '/':
//                    //                if (b == '<') {
//                    sb.append('\\');
//                    //                }
//                    sb.append(c);
//                    break;
//                case '\b':
//                    sb.append("\\b");
//                    break;
//                case '\t':
//                    sb.append("\\t");
//                    break;
//                case '\n':
//                    sb.append("\\n");
//                    break;
//                case '\f':
//                    sb.append("\\f");
//                    break;
//                case '\r':
//                    sb.append("\\r");
//                    break;
//                default:
//                    if (c < ' ') {
//                        t = "000" + Integer.toHexString(c);
//                        sb.append("\\u" + t.substring(t.length() - 4));
//                    } else {
//                        sb.append(c);
//                    }
//            }
//        }
//        sb.append('"');
//        return sb.toString();
    }
}
