package eu.dnetlib.enabling.datasources;

import java.text.ParseException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.CronExpression;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.transaction.annotation.Transactional;

import com.google.common.collect.ImmutableMap;

import eu.dnetlib.enabling.datasources.common.Api;
import eu.dnetlib.enabling.datasources.common.ApiParam;
import eu.dnetlib.enabling.datasources.common.Datasource;
import eu.dnetlib.enabling.datasources.common.DsmException;
import eu.dnetlib.enabling.datasources.common.Identity;
import eu.dnetlib.enabling.datasources.common.Organization;
import eu.dnetlib.enabling.datasources.common.PidSystem;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpDocumentNotFoundException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.locators.UniqueServiceLocator;

public class DatasourceManagerClients {

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

	private static final Resource dsQuery = new ClassPathResource(LocalOpenaireDatasourceManagerImpl.QUERY_BASEDIR + "getDatasource.sql");
	private static final Resource dsQueryByPrefix = new ClassPathResource(LocalOpenaireDatasourceManagerImpl.QUERY_BASEDIR + "getDatasourceByPrefix.sql");
	private static final Resource dsIdentitiesQuery = new ClassPathResource(LocalOpenaireDatasourceManagerImpl.QUERY_BASEDIR + "dsIdentitiesQuery.sql");
	private static final Resource dsOrganizationsQuery = new ClassPathResource(LocalOpenaireDatasourceManagerImpl.QUERY_BASEDIR + "dsOrganizationsQuery.sql");
	private static final Resource listApisByDsId = new ClassPathResource(LocalOpenaireDatasourceManagerImpl.QUERY_BASEDIR + "listApisByDsId.sql");
	private static final Resource isDefinedParamQuery = new ClassPathResource(LocalOpenaireDatasourceManagerImpl.QUERY_BASEDIR + "isDefinedParam.sql");

	private NamedParameterJdbcTemplate jdbcTemplate;

	@Autowired
	private UniqueServiceLocator serviceLocator;

	@Transactional(readOnly = true)
	public List<Map<String, Object>> searchSQL(final String sql, final Map<String, Object> sqlParams) throws DsmException {
		try {
			log.debug("Executing SQL: " + sql);
			return jdbcTemplate.queryForList(sql, sqlParams);
		} catch (final Exception e) {
			log.error("Error executing sql", e);

			throw new DsmException(-1, "Error obtaining datasources from db", e);
		}
	}

	@Transactional(readOnly = true)
	public List<Map<String, Object>> searchSQL(final Resource sqlResource, final Map<String, Object> sqlParams) throws DsmException {
		try {
			return searchSQL(IOUtils.toString(sqlResource.getInputStream()), sqlParams);
		} catch (final Exception e) {
			log.error("Error executing sql", e);
			throw new DsmException(-1, "Error obtaining datasources from db", e);
		}
	}

	@Transactional
	public void updateSQL(final String dsId, final String sql, final Map<String, Object> sqlparams)
		throws DsmException {
		log.debug("Executing query SQL: " + sql);

		jdbcTemplate.update(sql, sqlparams);
	}

	@Transactional
	public void updateSQL(final String dsId, final Resource sqlResource, final Map<String, Object> sqlparams)
		throws DsmException {
		try {
			updateSQL(dsId, IOUtils.toString(sqlResource.getInputStream()), sqlparams);
		} catch (final Exception e) {
			log.error("Error in updateSQL", e);
			throw new DsmException(-1, "Error in updateSQL", e);
		}
	}

	@Transactional(readOnly = true)
	public Datasource<Organization<?>, Identity, PidSystem> getDatasourceById(final String id) throws DsmException {
		final List<Map<String, Object>> list = searchSQL(dsQuery, ImmutableMap.of("dsId", id));

		if (list.size() != 1) { throw new DsmException("Invalid number of ds with id: " + id); }

		final Datasource<Organization<?>, Identity, PidSystem> ds = DatasourceFunctions.mapToDatasource(list.get(0));
		ds.setIdentities(searchSQL(dsIdentitiesQuery, ImmutableMap.of("dsId", id))
			.stream()
			.map(DatasourceFunctions::mapToDsIdentity)
			.collect(Collectors.toSet()));
		ds.setOrganizations(searchSQL(dsOrganizationsQuery, ImmutableMap.of("dsId", id))
			.stream()
			.map(DatasourceFunctions::mapToDsOrganization)
			.collect(Collectors.toSet()));

		return ds;
	}

	public Datasource<Organization<?>, Identity, PidSystem> getDatasourceByPrefix(final String prefix) throws DsmException {
		final List<Map<String, Object>> list = searchSQL(dsQueryByPrefix, ImmutableMap.of("prefix", prefix));

		if (list.size() != 1) { throw new DsmException("Invalid number of ds with prefix: " + prefix); }

		final Datasource<Organization<?>, Identity, PidSystem> ds = DatasourceFunctions.mapToDatasource(list.get(0));
		ds.setIdentities(searchSQL(dsIdentitiesQuery, ImmutableMap.of("dsId", ds.getId()))
			.stream()
			.map(DatasourceFunctions::mapToDsIdentity)
			.collect(Collectors.toSet()));
		ds.setOrganizations(searchSQL(dsOrganizationsQuery, ImmutableMap.of("dsId", ds.getId()))
			.stream()
			.map(DatasourceFunctions::mapToDsOrganization)
			.collect(Collectors.toSet()));

		return ds;
	}

	@Transactional(readOnly = true)
	public List<Api<ApiParam>> getApis(final String dsId) throws DsmException {

		return searchSQL(listApisByDsId, ImmutableMap.of("dsId", dsId))
			.stream()
			.map(DatasourceFunctions::mapToApi)
			.collect(Collectors.toList());
	}

	@Transactional(readOnly = true)
	public boolean isDefinedParam(final String apiId, final String param) throws DsmException {
		return !searchSQL(isDefinedParamQuery, ImmutableMap.of("apiId", apiId, "param", param)).isEmpty();
	}

	public Date findNextScheduledExecution(final String dsId, final String ifaceId) throws DsmException {
		final String xquery = "/*[.//DATAPROVIDER/@interface='" + ifaceId + "' and .//SCHEDULING/@enabled='true']//CRON/text()";
		try {
			final String cronExpression = serviceLocator.getService(ISLookUpService.class).getResourceProfileByQuery(xquery);
			final CronExpression cron = new CronExpression(cronExpression);
			return cron.getNextValidTimeAfter(new Date());
		} catch (final ISLookUpDocumentNotFoundException e) {
			// When the value is not found a null value must be returned
			return null;
		} catch (final ISLookUpException e) {
			log.error("Error in xquery: " + xquery, e);
			throw new DsmException(-1, "Error in xquery: " + xquery, e);
		} catch (final ParseException e) {
			log.error("Error parsing cron expression", e);
			throw new DsmException(-1, "Error parsing cron expression", e);
		}
	}

	public NamedParameterJdbcTemplate getJdbcTemplate() {
		return jdbcTemplate;
	}

	@Required
	public void setJdbcTemplate(final NamedParameterJdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

}
