package eu.dnetlib.openaire.dsm.dao;

import static eu.dnetlib.openaire.common.ExporterConstants.OAI;
import static eu.dnetlib.openaire.common.ExporterConstants.SET;
import static eu.dnetlib.openaire.dsm.dao.DatasourceSpecs.apiSpec;
import static eu.dnetlib.openaire.dsm.dao.DatasourceSpecs.dsRegisteredbyNotNullSpec;
import static eu.dnetlib.openaire.dsm.dao.DatasourceSpecs.dsSpec;

import java.sql.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import javax.persistence.EntityNotFoundException;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.google.common.collect.Lists;

import eu.dnetlib.DnetOpenaireExporterProperties;
import eu.dnetlib.enabling.datasources.common.DsmException;
import eu.dnetlib.enabling.datasources.common.DsmForbiddenException;
import eu.dnetlib.enabling.datasources.common.DsmNotFoundException;
import eu.dnetlib.openaire.dsm.domain.db.ApiDbEntry;
import eu.dnetlib.openaire.dsm.domain.db.ApiParamDbEntry;
import eu.dnetlib.openaire.dsm.domain.db.DatasourceApiDbEntry;
import eu.dnetlib.openaire.dsm.domain.db.DatasourceDbEntry;
import eu.dnetlib.openaire.exporter.model.dsm.RequestFilter;
import eu.dnetlib.openaire.exporter.model.dsm.RequestSort;
import eu.dnetlib.openaire.exporter.model.dsm.RequestSortOrder;

/**
 * Created by claudio on 20/10/2016.
 */
@Component
@ConditionalOnProperty(value = "openaire.exporter.enable.dsm", havingValue = "true")
public class DatasourceDaoImpl implements DatasourceDao<DatasourceDbEntry, ApiDbEntry> {

	private static final Log log = LogFactory.getLog(DatasourceDao.class);

	@Autowired
	private DnetOpenaireExporterProperties config;

	@Autowired
	private DatasourceDbEntryRepository dsRepository;

	@Autowired
	private ApiDbEntryRepository apiRepository;

	@Autowired
	private DatasourceApiDbEntryRepository dsApiRepository;

	// @Autowired
	// private VocabularyClient vocabularyClient;

	@Override
	public Page<DatasourceDbEntry> search(final RequestSort requestSortBy,
		final RequestSortOrder order,
		final RequestFilter requestFilter,
		final int page,
		final int size)
		throws DsmException {

		final Specification<DatasourceDbEntry> spec = dsSpec(requestSortBy, order, requestFilter);
		return dsRepository.findAll(spec, PageRequest.of(page, size));
	}

	@Override
	public Page<DatasourceDbEntry> searchRegistered(final RequestSort requestSortBy,
		final RequestSortOrder order,
		final RequestFilter requestFilter,
		final int page,
		final int size)
		throws DsmException {

		final Specification<DatasourceDbEntry> spec = dsSpec(requestSortBy, order, requestFilter).and(dsRegisteredbyNotNullSpec());
		return dsRepository.findAll(spec, PageRequest.of(page, size));
	}

	@Override
	public DatasourceDbEntry getDs(final String dsId) throws DsmException {
		return dsRepository.findById(dsId).orElseThrow(() -> new DsmException("Datasource not found. ID: " + dsId));
	}

	@Override
	public DatasourceDbEntry getDsByNsPrefix(final String prefix) throws DsmException {
		return dsRepository.findByNamespaceprefix(prefix).orElseThrow(() -> new DsmException("Datasource not found. NS Prefix: " + prefix));
	}

	@Override
	public void setManaged(final String id, final boolean managed) {
		log.info(String.format("setting managed = '%s' for ds '%s'", managed, id));
		dsRepository.setManaged(id, managed);
		apiRepository.setRemovable(id, true);
	}

	@Override
	public boolean isManaged(final String id) {
		return dsRepository.isManaged(id);
	}

	@Override
	public void updateCompliance(final String dsId, final String apiId, final String compliance, final boolean override) {
		log.info(String.format("setting compatibility = '%s' for ds '%s'", compliance, apiId));
		apiRepository.updateCompatibility(apiId, compliance);
	}

	@Override
	public List<ApiDbEntry> getApis(final String dsId) {
		return apiRepository.findByDatasource(dsId);
	}

	@Override
	public void deleteApi(final String dsId, final String apiId) throws DsmForbiddenException, DsmNotFoundException {
		final ApiDbEntry api = apiRepository.findById(apiId).orElseThrow(() -> new DsmNotFoundException("Api not found. ID: " + apiId));
		try {
			if (!api.getRemovable()) { throw new DsmForbiddenException(HttpStatus.SC_UNAUTHORIZED, "api is not removable"); }

			apiRepository.deleteById(apiId);
			log.info(String.format("deleted api '%s'", apiId));
		} catch (final EntityNotFoundException e) {
			throw new DsmNotFoundException(HttpStatus.SC_NOT_FOUND, "api not found");
		}
	}

	@Override
	public void addApi(final ApiDbEntry api) {
		apiRepository.save(api);
	}

	@Override
	public boolean existDs(final String dsId) throws DsmException {
		return dsRepository.existsById(dsId);
	}

	@Override
	public void saveDs(final DatasourceDbEntry d) {
		log.info(String.format("saving datasource '%s'", d.getId()));

		final DatasourceDbEntry datasource = dsRepository.save(d);
		log.info(String.format("saved datasource '%s'", datasource.getId()));
		ensureRegistrationDate(d.getId());
	}

	@Override
	public void deleteDs(final String dsId) {
		dsRepository.deleteById(dsId);
		log.info(String.format("deleted datasource '%s'", dsId));
	}

	@Override
	public void updateName(final String dsId, final String officialname, final String englishname) {
		// TODO what if one of the two names is null or empty?
		dsRepository.setDatasourcename(dsId, officialname, englishname);
	}

	@Override
	public void updateLogoUrl(final String dsId, final String logourl) throws DsmException {
		dsRepository.setLogoUrl(dsId, logourl);
	}

	@Override
	public void updateCoordinates(final String dsId, final Double latitude, final Double longitude) {
		dsRepository.setCoordinates(dsId, latitude, longitude);
	}

	@Override
	public void updateApiBaseUrl(final String apiId, final String baseurl) {
		apiRepository.setBaseurl(apiId, baseurl);
	}

	@Override
	@Transactional
	public boolean upsertApiOaiSet(final String apiId, final String oaiSet) throws DsmException {
		final ApiDbEntry api = apiRepository.findById(apiId).orElseThrow(() -> new DsmNotFoundException("Api not found. ID: " + apiId));
		if (OAI.equalsIgnoreCase(api.getProtocol())) {
			final Set<ApiParamDbEntry> apiParams = api.getApiParams();

			if (!apiParams.stream().anyMatch(ap -> SET.equals(ap.getParam()))) {
				apiRepository.addApiParam(apiId, SET, oaiSet);
				log.info(String.format("added api '%s' oai set with '%s'", apiId, oaiSet));
				return true;
			} else {
				apiRepository.updateOaiSet(apiId, oaiSet);
				log.info(String.format("updated api '%s' oai set with '%s'", apiId, oaiSet));
				return false;
			}
		} else {
			throw new DsmException(String.format("won't add OAI set to a non OAI interface: '%s' has protocol '%s'", apiId, api.getProtocol()));
		}
	}

	@Override
	public List<String> findApiBaseURLs(final RequestFilter requestFilter, final int page, final int size) throws DsmException {
		final PageRequest pageable = PageRequest.of(page, size);
		final Specification<DatasourceApiDbEntry> spec = apiSpec(requestFilter);
		final Set<String> set = dsApiRepository.findAll(spec, pageable)
			.getContent()
			.stream()
			.map(DatasourceApiDbEntry::getBaseurl)
			.filter(StringUtils::isNotBlank)
			.collect(Collectors.toCollection(HashSet::new));
		return Lists.newArrayList(set);
	}

	@Override
	public void updateTimezone(final String dsId, final String timezone) {
		dsRepository.setTimezone(dsId, timezone);
	}

	@Override
	public void updateEoscDatasourceType(final String dsId, final String type) throws DsmException {
		/*
		 * final Vocabulary typologies = vocabularyClient.getDatasourceTypologies(); if (!typologies.hasCode(type)) { throw new
		 * DsmException( HttpStatus.SC_BAD_REQUEST, String.format("invalid datasource type '%s', provide one according to vocabulary %s",
		 * type, config.getVocabularies() .getDatasourceTypologiesEndpoint())); }
		 */
		dsRepository.setEoscDatasourceType(dsId, type);
	}

	@Override
	public void updateRegisteringUser(final String dsId, final String registeredBy) throws DsmException {

		ensureRegistrationDate(dsId);

		dsRepository.setRegisteringUser(dsId, registeredBy);

	}

	@Override
	public void updatePlatform(final String dsId, final String platform) throws DsmException {
		dsRepository.setPlatform(dsId, platform);
	}

	// HELPER
	private void ensureRegistrationDate(final String dsId) {
		if (!dsRepository.hasRegistrationdate(dsId)) {
			log.info("setting registration date for datasource: " + dsId);
			dsRepository.setRegistrationDate(dsId, new Date(System.currentTimeMillis()));
		}
	}

}
