package eu.eudat.logic.managers;

import eu.eudat.data.dao.criteria.FunderCriteria;
import eu.eudat.data.entities.Funder;
import eu.eudat.data.query.items.table.grant.GrantTableRequest;
import eu.eudat.exceptions.grant.GrantWithDMPsDeleteException;
import eu.eudat.logic.builders.model.models.GrantBuilder;
import eu.eudat.data.dao.entities.GrantDao;
import eu.eudat.data.entities.DMP;
import eu.eudat.logic.proxy.config.ExternalUrlCriteria;
import eu.eudat.logic.services.operations.DatabaseRepository;
import eu.eudat.logic.utilities.helpers.ListHelper;
import eu.eudat.models.data.external.ExternalSourcesItemModel;
import eu.eudat.models.data.external.GrantsExternalSourcesModel;
import eu.eudat.models.data.grant.Grant;
import eu.eudat.models.data.helpers.common.DataTableData;
import eu.eudat.data.query.items.item.grant.GrantCriteriaRequest;
import eu.eudat.models.data.grant.GrantListingModel;
import eu.eudat.models.data.security.Principal;
import eu.eudat.logic.proxy.config.exceptions.HugeResultSet;
import eu.eudat.logic.proxy.config.exceptions.NoURLFound;
import eu.eudat.logic.proxy.fetching.RemoteFetcher;
import eu.eudat.queryable.QueryableList;
import eu.eudat.logic.services.ApiContext;
import org.springframework.stereotype.Component;

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

@Component
public class GrantManager {

    private ApiContext apiContext;
    private DatabaseRepository databaseRepository;
//    private FileStorageService fileStorageService;
    private RemoteFetcher remoteFetcher;
    private ListHelper listHelper;

    public GrantManager(ApiContext apiContext, ListHelper listHelper) {
        this.apiContext = apiContext;
        this.databaseRepository = apiContext.getOperationsContext().getDatabaseRepository();
//        this.fileStorageService = apiContext.getOperationsContext().getFileStorageService();
        this.remoteFetcher = apiContext.getOperationsContext().getRemoteFetcher();
        this.listHelper = listHelper;
    }

    public DataTableData<eu.eudat.models.data.grant.GrantListingModel> getPaged(GrantTableRequest grantTableRequest, Principal principal, String fieldsGroup) throws Exception {
        eu.eudat.data.entities.UserInfo userInfo = new eu.eudat.data.entities.UserInfo();
        userInfo.setId(principal.getId());
        GrantDao grantRepository = databaseRepository.getGrantDao();
        QueryableList<eu.eudat.data.entities.Grant> items = grantRepository.getWithCriteria(grantTableRequest.getCriteria());
        QueryableList<eu.eudat.data.entities.Grant> authItems = grantRepository.getAuthenticated(items, userInfo);
        QueryableList<eu.eudat.data.entities.Grant> pagedItems = PaginationManager.applyPaging(authItems, grantTableRequest);
        DataTableData<eu.eudat.models.data.grant.GrantListingModel> dataTable = new DataTableData<>();
        CompletableFuture grantsFuture;
        if (fieldsGroup.equals("listing")) {
            grantsFuture = pagedItems.selectAsync(item -> new GrantListingModel().fromDataModelDetails(item))
                    .whenComplete((results, throwable) ->
                            dataTable.setData(results)
                    );
        } else {
            grantsFuture = pagedItems.selectAsync(item -> new GrantListingModel().fromDataModel(item))
                    .whenComplete((results, throwable) ->
                        dataTable.setData(results)
                    );
        }
        CompletableFuture countFuture = pagedItems.countAsync().whenComplete((count, throwable) -> dataTable.setTotalCount(count));

        CompletableFuture.allOf(grantsFuture, countFuture).join();
        return dataTable;
    }

    public DataTableData<eu.eudat.models.data.grant.GrantListingModel> getPublicPaged(GrantTableRequest grantTableRequest) throws Exception {
        GrantDao grantRepository = databaseRepository.getGrantDao();
        grantTableRequest.getCriteria().setPublic(true);
        QueryableList<eu.eudat.data.entities.Grant> items = grantRepository.getWithCriteria(grantTableRequest.getCriteria());
        QueryableList<eu.eudat.data.entities.Grant> pagedItems = PaginationManager.applyPaging(items, grantTableRequest);
        DataTableData<eu.eudat.models.data.grant.GrantListingModel> dataTable = new DataTableData<>();
        CompletableFuture grantsFuture;
        grantsFuture = pagedItems.selectAsync(item -> new GrantListingModel().fromDataModel(item))
                .whenComplete((results, throwable) -> {
                    dataTable.setData(results);
                });
        CompletableFuture countFuture = pagedItems.countAsync().whenComplete((count, throwable) -> dataTable.setTotalCount(count));

        CompletableFuture.allOf(grantsFuture, countFuture).join();
        return dataTable;
    }

    public eu.eudat.models.data.grant.Grant getSingle(String id) throws InstantiationException, IllegalAccessException {
        eu.eudat.models.data.grant.Grant grant = new eu.eudat.models.data.grant.Grant();
        grant.fromDataModel(databaseRepository.getGrantDao().find(UUID.fromString(id)));
        return grant;
    }

    /*public eu.eudat.data.entities.Grant inactivate(String id) throws InstantiationException, IllegalAccessException {
        GrantDao grantRepository = databaseRepository.getGrantDao();
        eu.eudat.data.entities.Grant grant = grantRepository.find(UUID.fromString(id));
        grant.setStatus(eu.eudat.data.entities.Grant.Status.DELETED.getValue());
        grant = grantRepository.createOrUpdate(grant);
        return grant;
    }*/

    public List<eu.eudat.models.data.grant.Grant> getCriteriaWithExternal(GrantCriteriaRequest grantCriteria, Principal principal) throws HugeResultSet, NoURLFound {
        eu.eudat.data.entities.UserInfo userInfo = new eu.eudat.data.entities.UserInfo();
        userInfo.setId(principal.getId());
        /*if (grantCriteria.getCriteria().getFunderReference() != null && !grantCriteria.getCriteria().getFunderReference().trim().isEmpty()) {
            FunderCriteria funderCriteria = new FunderCriteria();
            funderCriteria.setReference(grantCriteria.getCriteria().getFunderReference());
            Funder funder = apiContext.getOperationsContext().getDatabaseRepository().getFunderDao().getWithCritetia(funderCriteria).getSingleOrDefault();
            if (funder != null) {
                grantCriteria.getCriteria().setFunderId(funder.getId().toString());
            }
        }*/
        ExternalUrlCriteria externalUrlCriteria = new ExternalUrlCriteria(grantCriteria.getCriteria().getLike());
        if (grantCriteria.getCriteria().getFunderReference() != null) {
            externalUrlCriteria.setFunderId(grantCriteria.getCriteria().getFunderReference());
            grantCriteria.getCriteria().setFunderReference(null);
        }
        grantCriteria.getCriteria().setReference("dmp:");
        QueryableList<eu.eudat.data.entities.Grant> items = apiContext.getOperationsContext().getDatabaseRepository().getGrantDao().getWithCriteria(grantCriteria.getCriteria());
        QueryableList<eu.eudat.data.entities.Grant> authItems = apiContext.getOperationsContext().getDatabaseRepository().getGrantDao().getAuthenticated(items, userInfo);
        List<eu.eudat.models.data.grant.Grant> grants = authItems.select(item -> new Grant().fromDataModel(item));

        List<Map<String, String>> remoteRepos = remoteFetcher.getGrants(externalUrlCriteria);

        GrantsExternalSourcesModel grantsExternalSourcesModel = new GrantsExternalSourcesModel().fromExternalItem(remoteRepos);
        for (ExternalSourcesItemModel externalListingItem : grantsExternalSourcesModel) {
            eu.eudat.models.data.grant.Grant grant = apiContext.getOperationsContext().getBuilderFactory().getBuilder(GrantBuilder.class)
                    .reference(externalListingItem.getRemoteId()).label(externalListingItem.getName())
                    .description(externalListingItem.getDescription()).uri(externalListingItem.getUri())
                    .key(externalListingItem.getKey())
                    .abbreviation(externalListingItem.getAbbreviation()).status(eu.eudat.data.entities.Grant.Status.fromInteger(0))
                    .source(externalListingItem.getTag())
                    .build();

            grants.add(grant);
        }
        grants.sort(Comparator.comparing(Grant::getLabel));
        grants = grants.stream().filter(listHelper.distinctByKey(Grant::getLabel)).collect(Collectors.toList());
        return grants;
    }

    public List<eu.eudat.models.data.grant.Grant> getCriteria(GrantCriteriaRequest grantCriteria) throws IllegalAccessException, InstantiationException, HugeResultSet, NoURLFound {
        GrantDao grantRepository = databaseRepository.getGrantDao();
        QueryableList<eu.eudat.data.entities.Grant> items = grantRepository.getWithCriteria(grantCriteria.getCriteria());
        if (grantCriteria.getLength() != null) items.take(grantCriteria.getLength());
        List<eu.eudat.models.data.grant.Grant> grants = items.select(item -> new Grant().fromDataModel(item));
        return grants;
    }

    /*public void createOrUpdate(eu.eudat.models.data.grant.Grant grant, Principal principal) throws ParseException, IOException {
        eu.eudat.data.entities.Grant grantEntity = grant.toDataModel();
        if (grant.getFiles() != null) {
            for (ContentFile file : grant.getFiles()) {
                try {
                    ContentFile storedFile = fileStorageService.copyFromTempFileSystem(file);
                    Content content = new ContentBuilder().extension(file.getType())
                            .label(file.getFilename())
                            .locationType(Content.LocationType.INTERNAL.getValue())
                            .parentType(Content.ParentType.GRANT.getValue())
                            .uri("LOCAL:" + storedFile.getId())
                            .build();
                    grantEntity.setContent(databaseRepository.getContentDao().createOrUpdate(content));
                } catch (TempFileNotFoundException e) {
                    continue;
                }
            }
        }
        grantEntity.setType(eu.eudat.data.entities.Grant.GrantType.INTERNAL.getValue());
        grantEntity.setCreationUser(databaseRepository.getUserInfoDao().find(principal.getId()));
        databaseRepository.getGrantDao().createOrUpdate(grantEntity);
    }*/

    public void delete(UUID uuid) {
        eu.eudat.data.entities.Grant oldGrant = apiContext.getOperationsContext().getDatabaseRepository().getGrantDao().find(uuid);
        if (oldGrant.getDmps().size() > 0)
            throw new GrantWithDMPsDeleteException("You cannot Remove Grants with DMPs");
        oldGrant.setStatus(DMP.DMPStatus.DELETED.getValue());
        apiContext.getOperationsContext().getDatabaseRepository().getGrantDao().createOrUpdate(oldGrant);
    }
}
