package eu.dnetlib.data.datasource;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import eu.dnetlib.enabling.datasources.common.*;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.is.registry.rmi.ISRegistryException;
import eu.dnetlib.enabling.is.registry.rmi.ISRegistryService;
import eu.dnetlib.enabling.locators.UniqueServiceLocator;
import org.antlr.stringtemplate.StringTemplate;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpStatus;
import org.dom4j.Document;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.core.io.ClassPathResource;

import java.io.StringReader;
import java.util.*;
import java.util.stream.Collectors;

public class LocalSimpleDatasourceManagerImpl implements LocalSimpleDatasourceManager {

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

    private static final String REPOSITORY_RESOURCE_TYPE = "RepositoryServiceResourceType";

    private ClassPathResource findReposQueryTmpl = new ClassPathResource("/eu/dnetlib/enabling/datasources/templates/findRepos.xquery.st");

    private ClassPathResource browseRepoApisQueryTmpl = new ClassPathResource("/eu/dnetlib/enabling/datasources/templates/browseRepoApis.xquery.st");
    private ClassPathResource findRepoApisQueryTmpl = new ClassPathResource("/eu/dnetlib/enabling/datasources/templates/findRepoApis.xquery.st");
    private ClassPathResource findReposMapQuery = new ClassPathResource("/eu/dnetlib/enabling/datasources/templates/findReposMap.xquery");
    private ClassPathResource simpleFindReposQueryTmpl = new ClassPathResource("/eu/dnetlib/enabling/datasources/templates/simpleFindRepos.xquery.st");

    private List<XmlBrowsableField> browsableFields;

    @Autowired
    private UniqueServiceLocator serviceLocator;

    @Override
    public Set<String> listManagedDatasourceIds() throws DsmRuntimeException {
        throw new DsmRuntimeException("method 'listManagedDatasourceIds' not implemented");
    }

    @Override
    public List<SimpleDatasource> searchDatasourcesByType(String type) throws DsmException {
        try {
            final List<SimpleDatasource> list = new ArrayList<>();

            final StringTemplate st = new StringTemplate(IOUtils.toString(simpleFindReposQueryTmpl.getInputStream()));
            st.setAttribute("type", type);

            for (String s : serviceLocator.getService(ISLookUpService.class).quickSearchProfile(st.toString())) {
                final SimpleDatasource r = new SimpleDatasource();
                final String[] arr = s.split("@=@");
                r.setId(arr[0].trim());
                r.setName(arr[1].trim());
                r.setOrigId(arr[2].trim());
                r.setApis(Sets.newHashSet(arr[3].replaceAll("\\s", "").split(",")));
                r.setValid("true".equals(arr[4].trim()));
                r.setTypology(type);
                list.add(r);
            }

            Collections.sort(list);

            return list;
        } catch (Exception e) {
            log.error("Error listing repos", e);
        }
        return Lists.newArrayList();
    }

    @Override
    public List<? extends SearchApisEntry> searchApis(String field, Object value) throws DsmException {
        try {
            final StringTemplate st = new StringTemplate();
            st.setTemplate(IOUtils.toString(findRepoApisQueryTmpl.getInputStream()));

            final String val = value.toString();
            if (field.equalsIgnoreCase("__search__")) {
                    st.setAttribute("cond", "contains(../..//(*|@*)/lower-case(.), '" + StringEscapeUtils.escapeXml(val.toLowerCase()) + "')");
            } else {
                final XmlBrowsableField f = findBrowseField(field);
                if (f != null) {
                    st.setAttribute("cond", f.getXpath() + "='" + StringEscapeUtils.escapeXml(val) + "'");
                } else {
                    return Lists.newArrayList();
                }
            }

            final String query = st.toString();
            final SAXReader reader = new SAXReader();
            final List<String> list = serviceLocator.getService(ISLookUpService.class).quickSearchProfile(query);

            return list.stream()
                    .map(s -> {
                        final SearchApisEntry iface = new SearchApisEntry();
                        try {
                            final Document doc = reader.read(new StringReader(s));
                            final String country = doc.valueOf("//REPO/@country");

                            iface.setRepoId(doc.valueOf("//REPO/@id"));
                            iface.setRepoCountry(StringUtils.isEmpty(country) ? "-" : country.toUpperCase());
                            iface.setRepoName(doc.valueOf("//REPO/@name"));
                            iface.setRepoPrefix(doc.valueOf("//REPO/@prefix"));

                            final Node ifcNode = doc.selectSingleNode("//INTERFACE");

                            iface.setId(ifcNode.valueOf("./@id"));
                            iface.setActive(Boolean.valueOf(ifcNode.valueOf("./@active")));
                            iface.setProtocol(ifcNode.valueOf("./ACCESS_PROTOCOL/text()"));

                            final String overCompliance = ifcNode.valueOf("./INTERFACE_EXTRA_FIELD[@name='overriding_compliance']");
                            if (StringUtils.isEmpty(overCompliance)) {
                                iface.setCompliance(ifcNode.valueOf("@compliance"));
                            } else {
                                iface.setCompliance(overCompliance);
                            }

                            final String lastAggregationDate = ifcNode.valueOf("./INTERFACE_EXTRA_FIELD[@name='last_aggregation_date']");
                            if (!StringUtils.isEmpty(lastAggregationDate)) {
                                iface.setAggrDate(lastAggregationDate);
                            } else {
                                final String lastDownloadDate = ifcNode.valueOf("./INTERFACE_EXTRA_FIELD[@name='last_download_date']");
                                if (!StringUtils.isEmpty(lastDownloadDate)) {
                                    iface.setAggrDate(lastDownloadDate);
                                }
                            }
                            final String lastAggregationTotal = ifcNode.valueOf("./INTERFACE_EXTRA_FIELD[@name='last_aggregation_total']");
                            if (StringUtils.isEmpty(lastAggregationTotal)) {
                                final String lastDownloadTotal = ifcNode.valueOf("./INTERFACE_EXTRA_FIELD[@name='last_download_total']");
                                if (StringUtils.isEmpty(lastDownloadTotal)) {
                                    iface.setAggrTotal(0);
                                } else {
                                    iface.setAggrTotal(NumberUtils.toInt(lastDownloadTotal, 0));
                                }
                            } else {
                                iface.setAggrTotal(NumberUtils.toInt(lastAggregationTotal, 0));
                            }
                        } catch (Exception e) {
                            log.error(e);
                        }
                        return iface;
                    }).collect(Collectors.toList());
        } catch (Exception e) {
            log.error("Error searching field " + field + " - value: " + value, e);
        }
        return Lists.newArrayList();
    }

    @Override
    public List<XmlBrowsableField> listBrowsableFields() throws DsmException {
        return getBrowsableFields();
    }

    public List<XmlBrowsableField> getBrowsableFields() {
        return browsableFields;
    }

    @Required
    public void setBrowsableFields(List<XmlBrowsableField> browsableFields) {
        this.browsableFields = browsableFields;
    }

    @Override
    public List<BrowseTerm> browseField(final String f) throws DsmException {
        final List<BrowseTerm> list = Lists.newArrayList();
        try {
            final XmlBrowsableField field = findBrowseField(f);
            if (field != null) {
                final StringTemplate st = new StringTemplate(IOUtils.toString(browseRepoApisQueryTmpl.getInputStream()));
                st.setAttribute("xpath", field.getXpath());

                final Map<String, String> terms = fetchVocabularyTerms(field.getVocabulary());

                for (String s : serviceLocator.getService(ISLookUpService.class).quickSearchProfile(st.toString())) {
                    final String[] arr = s.split("@-@-@");
                    final String id = arr[0].trim();
                    final int count = NumberUtils.toInt(arr[1].trim(), 0);
                    list.add(new BrowseTermImpl(id, findLabel(id, terms), count));
                }
            }
        } catch (Exception e) {
            log.error("Error browsing field " + f, e);
        }

        return list;
    }

    @Override
    public void setActive(String dsId, String apiId, boolean active) throws DsmException {
        throw new DsmRuntimeException("method 'setActive' not implemented");
    }

    @Override
    public boolean isActive(String dsId, String apiId) throws DsmException {
        throw new DsmRuntimeException("method 'isActive' not implemented");
    }

    @Override
    public void setLastCollectionInfo(String dsId, String apiId, String mdId, Integer size, Date date) throws DsmException {
        Map<String, String> updates = Maps.newHashMap();
        updates.put("last_collection_date", date.toString());
        updates.put("last_collection_total", size.toString());
        updateInterfaceExtraInfo(dsId, apiId, updates);
    }

    @Override
    public void setLastAggregationInfo(String dsId, String apiId, String mdId, Integer size, Date date) throws DsmException {
        Map<String, String> updates = Maps.newHashMap();
        updates.put("last_aggregation_date", date.toString());
        updates.put("last_aggregation_total", size.toString());
        updateInterfaceExtraInfo(dsId, apiId, updates);
    }

    @Override
    public void setLastDownloadInfo(String dsId, String apiId, String objId, Integer size, Date date) throws DsmException {
        Map<String, String> updates = Maps.newHashMap();
        updates.put("last_download_date", date.toString());
        updates.put("last_download_total", size.toString());
        updateInterfaceExtraInfo(dsId, apiId, updates);
    }

    @Override
    public void setLastValidationJob(String dsId, String apiId, String jobId) throws DsmException {
        Map<String, String> updates = Maps.newHashMap();
        updates.put("last_validation_job", jobId);
        updateInterfaceExtraInfo(dsId, apiId, updates);
    }

    @Override
    public void updateApiDetails(String dsId, String apiId, String metadataIdentifierPath, String baseUrl, Map<String, String> params) throws DsmException {

    // oaiset, baseurl, compatibility
/*
    <INTERFACE active="true" compliance="openaire3.0" contentDescription="metadata" id="api_________::opendoar____::2294::0" label="pubsrepository::institutional (openaire3.0)" removable="true" typology="pubsrepository::institutional">
        <ACCESS_PROTOCOL format="oai_dc" set="openaire">oai</ACCESS_PROTOCOL>
        <BASE_URL>http://pub.uni-bielefeld.de/oai</BASE_URL>
        <INTERFACE_EXTRA_FIELD name="metadata_identifier_path">//*[local-name()='header']/*[local-name()='identifier']</INTERFACE_EXTRA_FIELD>
 */


    }

    @Override
    public boolean isRemovable(String dsId, String apiId) throws DsmException {
        return false;
    }

    @Override
    public void regenerateProfiles() throws DsmException {

    }

    @Override
    public Datasource<Organization<?>, Identity> getDs(String id) throws DsmException {
        return null;
    }

    @Override
    public List<? extends Api<ApiParam>> getApis(String dsId) throws DsmException {
        return null;
    }

    @Override
    public void deleteDs(String dsId) throws DsmException {

    }

    @Override
    public void deleteApi(String dsId, String apiId) throws DsmException {

    }

    @Override
    public void addApi(Api<ApiParam> api) throws DsmException {

    }

    @Override
    public void setManaged(String id, boolean managed) throws DsmException {

    }

    @Override
    public boolean isManaged(String id) throws DsmException {
        return false;
    }

    @Override
    public void saveDs(Datasource<Organization<?>, Identity> datasource) throws DsmException {

    }

    @Override
    public void updateCompliance(String dsId, String apiId, String compliance, boolean override) throws DsmException {

    }

    // HELPERS

    private void updateInterfaceExtraInfo(String dsId, String apiId, Map<String, String> updates) throws DsmException {
        final String xpath = "//INTERFACE[@id = '%s']/INTERFACE_EXTRA_FIELD[@name='%s']";
        final ISRegistryService isRegistry = serviceLocator.getService(ISRegistryService.class);

        updates.forEach((k, v) -> {
            try {
                isRegistry.updateProfileNode(dsId, String.format(xpath, apiId, k), v);
            } catch (ISRegistryException e) {
                log.error("Error updating profile: " + dsId, e);
                throw new DsmRuntimeException("Error updating profile: " + dsId, e);
            }
        });
    }

    private void updateInterfaceExtraInfo(String dsId, String apiId, Integer size, Date date, String last_date, String last_total) throws DsmException {
        final String xpath = "//INTERFACE[@id = '%s']/INTERFACE_EXTRA_FIELD[@name='%s']";
        final ISRegistryService isRegistry = serviceLocator.getService(ISRegistryService.class);
        try {
            isRegistry.updateProfileNode(dsId, String.format(xpath, apiId, last_date), date.toString());
            isRegistry.updateProfileNode(dsId, String.format(xpath, apiId, last_total), size.toString());

        } catch (ISRegistryException e) {
            log.error("Error updating profile: " + dsId, e);
            throw new DsmException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Error updating profile: " + dsId, e);
        }
    }

    private XmlBrowsableField findBrowseField(final String id) throws DsmException {
        for (XmlBrowsableField f : listBrowsableFields()) {
            if (f.getId().equals(id)) {
                return f;
            }
        }
        return null;
    }

    public Map<String, String> fetchVocabularyTerms(final String voc) throws ISLookUpException {
        final String xquery = "for $x in collection('/db/DRIVER/VocabularyDSResources/VocabularyDSResourceType')[.//VOCABULARY_NAME/@code = '"
                + voc.trim() + "']//TERM return concat($x/@code, ' @@@ ', $x/@english_name)";

        final Map<String, String> map = Maps.newHashMap();
        for (String s : serviceLocator.getService(ISLookUpService.class).quickSearchProfile(xquery)) {
            final String[] arr = s.split("@@@");
            map.put(arr[0].trim(), arr[1].trim());
        }
        return map;
    }

    private String findLabel(final String code, final Map<String, String> terms) {
        return terms.containsKey(code) ? terms.get(code) : code;
    }

}
