package life.catalogue.dao;

import life.catalogue.api.model.Dataset;
import life.catalogue.api.model.Page;
import life.catalogue.api.model.ResultPage;
import life.catalogue.api.search.DatasetSearchRequest;
import life.catalogue.api.vocab.DatasetOrigin;
import life.catalogue.common.io.DownloadUtil;
import life.catalogue.db.DatasetPageable;
import life.catalogue.db.mapper.*;
import life.catalogue.es.name.index.NameUsageIndexService;
import life.catalogue.img.ImageService;
import life.catalogue.img.LogoUpdateJob;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.io.File;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;

public class DatasetDao extends EntityDao<Integer, Dataset, DatasetMapper> {
  
  @SuppressWarnings("unused")
  private static final Logger LOG = LoggerFactory.getLogger(DatasetDao.class);
  
  private final DownloadUtil downloader;
  private final ImageService imgService;
  private final BiFunction<Integer, String, File> scratchFileFunc;
  private final DatasetImportDao diDao;
  private final NameUsageIndexService indexService;
  
  public DatasetDao(SqlSessionFactory factory,
                    DownloadUtil downloader,
                    ImageService imgService,
                    DatasetImportDao diDao,
                    NameUsageIndexService indexService,
                    BiFunction<Integer, String, File> scratchFileFunc) {
    super(false, factory, DatasetMapper.class);
    this.downloader = downloader;
    this.imgService = imgService;
    this.scratchFileFunc = scratchFileFunc;
    this.diDao = diDao;
    this.indexService = indexService;
  }
  
  public ResultPage<Dataset> list(Page page) {
    return super.list(DatasetMapper.class, page);
  }
  
  public ResultPage<Dataset> search(@Nullable DatasetSearchRequest nullableRequest, @Nullable Page page) {
    page = page == null ? new Page() : page;
    final DatasetSearchRequest req = nullableRequest == null || nullableRequest.isEmpty() ? new DatasetSearchRequest() : nullableRequest;
    if (req.getSortBy() == null) {
      if (!StringUtils.isBlank(req.getQ())) {
        req.setSortBy(DatasetSearchRequest.SortBy.RELEVANCE);
      } else {
        req.setSortBy(DatasetSearchRequest.SortBy.KEY);
      }
    } else if (req.getSortBy() == DatasetSearchRequest.SortBy.RELEVANCE && StringUtils.isBlank(req.getQ())) {
      req.setQ(null);
      req.setSortBy(DatasetSearchRequest.SortBy.KEY);
    }
    
    try (SqlSession session = factory.openSession()){
      DatasetMapper dm = session.getMapper(DatasetMapper.class);
      List<Dataset> result = dm.search(req, page);
      return new ResultPage<>(page, result, () -> dm.count(req));
    }
  }
  
  @Override
  protected void deleteBefore(Integer key, Dataset old, int user, DatasetMapper mapper, SqlSession session) {
    // remove decisions, sectors and estimates
    for (Class<DatasetPageable<?>> mapperCLass : new Class[]{SectorMapper.class, DecisionMapper.class, EstimateMapper.class}) {
      session.getMapper(mapperCLass).deleteByDataset(key);
    }
    // delete data partitions
    Partitioner.delete(session, key);
    session.commit();
    // now also clear filesystem
    diDao.removeMetrics(key);
  }

  @Override
  protected void deleteAfter(Integer key, Dataset old, int user, DatasetMapper mapper, SqlSession session) {
    // clear search index asnychroneously
    CompletableFuture.supplyAsync(() -> indexService.deleteDataset(key))
            .exceptionally(e -> {
              LOG.error("Failed to delete ES docs for dataset {}", key, e.getCause());
              return 0;
            });
  }

  @Override
  protected void createAfter(Dataset obj, int user, DatasetMapper mapper, SqlSession session) {
    pullLogo(obj);
    if (obj.getOrigin() == DatasetOrigin.MANAGED) {
      recreatePartition(obj.getKey());
    }
  }
  
  @Override
  protected void updateAfter(Dataset obj, Dataset old, int user, DatasetMapper mapper, SqlSession session) {
    pullLogo(obj);
    if (obj.getOrigin() == DatasetOrigin.MANAGED && !session.getMapper(DatasetPartitionMapper.class).exists(obj.getKey())) {
      recreatePartition(obj.getKey());
    }
  }

  private void recreatePartition(int datasetKey) {
    Partitioner.partition(factory, datasetKey);
    Partitioner.indexAndAttach(factory, datasetKey);
  }
  private void pullLogo(Dataset d) {
    LogoUpdateJob.updateDatasetAsync(d, factory, downloader, scratchFileFunc, imgService);
  }
  
}

@@ -247,16 +247,59 @@
 Origin;%0A
+import life.catalogue.api.vocab.Frequency;%0A
 import l
@@ -3868,32 +3868,458 @@
        %7D);%0A  %7D%0A%0A
+  @Override%0A  public Integer create(Dataset obj, int user) %7B%0A    return super.create(resetOriginProps(obj), user);%0A  %7D%0A%0A  private static Dataset resetOriginProps(Dataset d) %7B%0A    // null properties not matching its origin%0A    switch (d.getOrigin()) %7B%0A      case MANAGED:%0A        d.setDataFormat(null);%0A      case UPLOADED:%0A        d.setDataAccess(null);%0A        d.setImportFrequency(Frequency.NEVER);%0A    %7D%0A    return d;%0A  %7D%0A%0A
   @Override%0A  pr
@@ -4522,34 +4522,234 @@
 y());%0A    %7D%0A  %7D%0A
+%0A
   
+@Override%0A  protected void updateBefore(Dataset obj, Dataset old, int user, DatasetMapper mapper, SqlSession session) %7B%0A    super.updateBefore(resetOriginProps(obj), old, user, mapper, session);%0A  %7D%0A
 %0A  @Override%0A  p
@@ -5336,13 +5336,11 @@
 e);%0A  %7D%0A
-  
 %0A%7D%0A

Manage data access and import frequency depending on origin