package eu.eudat.publicapi.managers;

import eu.eudat.data.entities.Dataset;
import eu.eudat.data.query.definition.helpers.ColumnOrderings;
import eu.eudat.elastic.criteria.DatasetCriteria;
import eu.eudat.elastic.repository.DatasetRepository;
import eu.eudat.exceptions.security.ForbiddenException;
import eu.eudat.logic.managers.PaginationManager;
import eu.eudat.logic.services.ApiContext;
import eu.eudat.logic.services.operations.DatabaseRepository;
import eu.eudat.logic.utilities.builders.XmlBuilder;
import eu.eudat.models.HintedModelFactory;
import eu.eudat.models.data.helpers.common.DataTableData;
import eu.eudat.models.data.user.composite.PagedDatasetProfile;
import eu.eudat.publicapi.criteria.mapper.DmpPublicCriteriaMapper;
import eu.eudat.publicapi.models.listingmodels.DatasetPublicListingModel;
import eu.eudat.publicapi.models.overviewmodels.DatasetPublicModel;
import eu.eudat.queryable.QueryableList;
import eu.eudat.types.grant.GrantStateType;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.transaction.Transactional;
import java.util.*;
import java.util.stream.Collectors;

@Component
public class DatasetPublicManager {
    private static final Logger logger = LoggerFactory.getLogger(DatasetPublicManager.class);

    private DatabaseRepository databaseRepository;
    private DatasetRepository datasetRepository;

    @Autowired
    public DatasetPublicManager(ApiContext apiContext){
        this.databaseRepository = apiContext.getOperationsContext().getDatabaseRepository();
        this.datasetRepository = apiContext.getOperationsContext().getElasticRepository().getDatasetRepository();
    }

    public DataTableData<DatasetPublicListingModel> getPublicPaged(eu.eudat.publicapi.request.dataset.DatasetPublicTableRequest datasetTableRequest) throws Exception {
        Long count = 0L;
        DatasetCriteria datasetCriteria = new DatasetCriteria();
        datasetCriteria.setPublic(true);
        datasetCriteria.setLike(datasetTableRequest.getCriteria().getLike());
        datasetCriteria.setDatasetTemplates(datasetTableRequest.getCriteria().getDatasetTemplates());
        datasetCriteria.setDmps(datasetTableRequest.getCriteria().getDmpIds());
        datasetCriteria.setGrants(datasetTableRequest.getCriteria().getGrants());
        datasetCriteria.setCollaborators(datasetTableRequest.getCriteria().getCollaborators());
        datasetCriteria.setAllowAllVersions(datasetTableRequest.getCriteria().getAllVersions());
        datasetCriteria.setOrganiztions(datasetTableRequest.getCriteria().getDmpOrganisations());
        if(datasetTableRequest.getCriteria().getTags() != null && !datasetTableRequest.getCriteria().getTags().isEmpty()){
            datasetCriteria.setHasTags(true);
            datasetCriteria.setTags(datasetTableRequest.getCriteria().getTags());
        }
        datasetCriteria.setGroupIds(datasetTableRequest.getCriteria().getGroupIds());
        datasetCriteria.setGrantStatus(GrantStateType.ONGOING.getValue().shortValue()); // grant status ongoing
        datasetCriteria.setStatus(Dataset.Status.FINALISED.getValue()); // dataset status finalized
        if (datasetTableRequest.getOrderings() != null) {
            datasetCriteria.setSortCriteria(DmpPublicCriteriaMapper.toElasticSorting(datasetTableRequest.getOrderings()));
        }
        datasetCriteria.setOffset(datasetTableRequest.getOffset());
        datasetCriteria.setSize(datasetTableRequest.getLength());
        List<eu.eudat.elastic.entities.Dataset> datasets;
        try {
            datasets = datasetRepository.exists() ?
                    datasetRepository.queryIds(datasetCriteria) : new LinkedList<>();
            if(datasetTableRequest.getCriteria().getPeriodStart() != null)
                datasets = datasets.stream().filter(dataset -> dataset.getCreated().after(datasetTableRequest.getCriteria().getPeriodStart())).collect(Collectors.toList());
            if(datasetTableRequest.getCriteria().getPeriodEnd() != null)
                datasets = datasets.stream().filter(dataset -> dataset.getCreated().before(datasetTableRequest.getCriteria().getPeriodEnd())).collect(Collectors.toList());
            count = (long) datasets.size();
        } catch (Exception ex) {
            logger.warn(ex.getMessage());
            datasets = null;
        }
        /*datasetTableRequest.setQuery(databaseRepository.getDatasetDao().asQueryable().withHint(HintedModelFactory.getHint(DatasetPublicListingModel.class)));
        QueryableList<Dataset> items = datasetTableRequest.applyCriteria();*/
        datasetTableRequest.setQuery(databaseRepository.getDatasetDao().asQueryable().withHint(HintedModelFactory.getHint(DatasetPublicListingModel.class)));
        QueryableList<Dataset> items;
        if (datasets != null) {
            if (!datasets.isEmpty()) {
                items = databaseRepository.getDatasetDao().asQueryable().withHint(HintedModelFactory.getHint(DatasetPublicListingModel.class));
                List<eu.eudat.elastic.entities.Dataset> finalDatasets = datasets;
                items.where((builder, root) -> root.get("id").in(finalDatasets.stream().map(x -> UUID.fromString(x.getId())).collect(Collectors.toList())));
            } else
                items = datasetTableRequest.applyCriteria();
            //items.where((builder, root) -> root.get("id").in(new UUID[]{UUID.randomUUID()}));
        } else {
            items = datasetTableRequest.applyCriteria();
        }

        List<String> strings = new ArrayList<>();
        strings.add("-dmp:publishedAt|join|");
        if(datasetTableRequest.getOrderings() != null) {
            datasetTableRequest.getOrderings().setFields(strings);
        }
        else{
            datasetTableRequest.setOrderings(new ColumnOrderings());
            datasetTableRequest.getOrderings().setFields(strings);
        }
        if (count == 0L) {
            count = items.count();
        }
        QueryableList<Dataset> pagedItems = PaginationManager.applyPaging(items, datasetTableRequest);
        DataTableData<DatasetPublicListingModel> dataTable = new DataTableData<>();

        List<DatasetPublicListingModel> datasetLists = pagedItems.
                select(this::mapPublicModel);

        dataTable.setData(datasetLists.stream().filter(Objects::nonNull).collect(Collectors.toList()));
        if(count <= datasetTableRequest.getLength())
            dataTable.setTotalCount(count);
        else
            dataTable.setTotalCount((long)datasetTableRequest.getLength());
        //CompletableFuture.allOf(countFuture).join();
        return dataTable;
    }

    public DatasetPublicModel getOverviewSinglePublic(String id) throws Exception {
        Dataset datasetEntity = databaseRepository.getDatasetDao().find(UUID.fromString(id));
        if (datasetEntity.getStatus() == Dataset.Status.DELETED.getValue()) {
            throw new Exception("Dataset is deleted.");
        }
        if (!datasetEntity.getDmp().isPublic()) {
            throw new ForbiddenException("Selected Dataset is not public");
        }
        DatasetPublicModel dataset = new DatasetPublicModel();
        dataset.setDatasetProfileDefinition(this.getPagedProfile(dataset.getStatus(), datasetEntity));
        dataset.fromDataModel(datasetEntity);

        return dataset;
    }

    @Transactional
    private DatasetPublicListingModel mapPublicModel(Dataset item) {
        /*if (item.getProfile() == null)
            return null;*/
        DatasetPublicListingModel listingPublicModel = new DatasetPublicListingModel().fromDataModel(item);
        /*DatasetProfileCriteria criteria = new DatasetProfileCriteria();
        criteria.setGroupIds(Collections.singletonList(item.getProfile().getGroupId()));
        List<DatasetProfile> profiles = apiContext.getOperationsContext().getDatabaseRepository().getDatasetProfileDao().getWithCriteria(criteria).toList();
        boolean islast = false;
        if (!profiles.isEmpty()) {
            profiles = profiles.stream().sorted(Comparator.comparing(DatasetProfile::getVersion)).collect(Collectors.toList());
            islast = profiles.get(0).getId().equals(item.getProfile().getId());
        }
        listingModel.setProfileLatestVersion(islast);*/
        return listingPublicModel;
    }

    private PagedDatasetProfile getPagedProfile(int status, Dataset datasetEntity){
        eu.eudat.models.data.user.composite.DatasetProfile datasetprofile = this.generateDatasetProfileModel(datasetEntity.getProfile());
        datasetprofile.setStatus(status);
        if (datasetEntity.getProperties() != null) {
            JSONObject jObject = new JSONObject(datasetEntity.getProperties());
            Map<String, Object> properties = jObject.toMap();
            datasetprofile.fromJsonObject(properties);
        }
        PagedDatasetProfile pagedDatasetProfile = new PagedDatasetProfile();
        pagedDatasetProfile.buildPagedDatasetProfile(datasetprofile);
        return pagedDatasetProfile;
    }

    private eu.eudat.models.data.user.composite.DatasetProfile generateDatasetProfileModel(eu.eudat.data.entities.DatasetProfile profile) {
        Document viewStyleDoc = XmlBuilder.fromXml(profile.getDefinition());
        Element root = (Element) viewStyleDoc.getDocumentElement();
        eu.eudat.models.data.entities.xmlmodels.datasetprofiledefinition.ViewStyleModel viewstyle = new eu.eudat.models.data.entities.xmlmodels.datasetprofiledefinition.ViewStyleModel().fromXml(root);

        eu.eudat.models.data.user.composite.DatasetProfile datasetprofile = new eu.eudat.models.data.user.composite.DatasetProfile();
        datasetprofile.buildProfile(viewstyle);

        return datasetprofile;
    }
}
