package eu.dnetlib.functionality.modular.ui.mdstore;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import javax.xml.ws.wsaddressing.W3CEndpointReference;

import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import eu.dnetlib.data.mdstore.DocumentNotFoundException;
import eu.dnetlib.data.mdstore.MDStoreService;
import eu.dnetlib.data.mdstore.modular.MDFormatDescription;
import eu.dnetlib.data.mdstore.modular.MDStoreUtils;
import eu.dnetlib.data.mdstore.modular.ModularMDStoreService;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpException;
import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.locators.UniqueServiceLocator;
import eu.dnetlib.enabling.resultset.client.ResultSetClientFactory;
import eu.dnetlib.functionality.modular.ui.mdstore.model.MDStoreInfo;
import eu.dnetlib.functionality.modular.ui.mdstore.model.MDStoreResult;
import eu.dnetlib.miscutils.functional.xml.ApplyXslt;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;

/**
 * The Class MDStoreServiceInternalController.
 */
@Controller
public class MDStoreServiceInternalController {

    /**
     * The Constant log.
     */
    private static final Log log = LogFactory.getLog(MDStoreServiceInternalController.class);

    @Value("${dnet.modular.ui.mdstore.xslt.record2document}")
    private String recordXsltClasspath;

    /**
     * The lookup locator.
     */
    @Resource
    private UniqueServiceLocator serviceLocator;

    @Autowired
    private MDStoreUtils mdStoreUtils;

    @Resource
    private ResultSetClientFactory resultSetClientFactory;

    @Autowired
    private ModularMDStoreService mdStoreService;


    private Map<String, Info> datasourceCache;

    /**
     * List md stores.
     *
     * @param map the map
     * @return the list
     * @throws Exception the exception
     */
    @RequestMapping(value = "/ui/mdstore/listMDStores.do")
    @ResponseBody

    public List<MDStoreInfo> listMDStores(final ModelMap map) throws Exception {
        if (datasourceCache == null) {
            datasourceCache = datasourceFromMdStore();
        }


        final String xquery = "for $x in  collection('/db/DRIVER/MDStoreDSResources/MDStoreDSResourceType')  let $format := $x//METADATA_FORMAT  "
                + "let $layout := $x//METADATA_FORMAT_LAYOUT  let $interpretation :=$x//METADATA_FORMAT_INTERPRETATION "
                + "let $uri := $x//RESOURCE_URI/@value/string()  let $id := $x//RESOURCE_IDENTIFIER/@value/string() "
                + "let $lastDate := $x//LAST_STORAGE_DATE  let $size :=$x//NUMBER_OF_RECORDS "
                + "return concat($uri,'@::@',$id,'@::@',$format,'(-)',$layout, '(-)',$interpretation,'@::@',$lastDate,'@::@',$size) ";

        log.debug("Executing lookup query" + xquery);
        return Lists.transform(serviceLocator.getService(ISLookUpService.class).quickSearchProfile(xquery), input -> {
            MDStoreInfo mdStoreInfo = MDStoreInfo.fromXqueryResult(input);
            Info currentInfo = datasourceCache.get(mdStoreInfo.getId());
            if (currentInfo != null) {
                Map<String, String> datasourcesInvolved = mdStoreInfo.getDatasourcesInvolved();
                if (datasourcesInvolved == null) {
                    datasourcesInvolved = Maps.newHashMap();
                    mdStoreInfo.setDatasourcesInvolved(datasourcesInvolved);
                }
                datasourcesInvolved.put("datasourceId", currentInfo.getDatasourceId());
                datasourcesInvolved.put("datasourceName", currentInfo.getDatasourceName());

            }
            return mdStoreInfo;
        });
    }

    @RequestMapping(value = "/ui/mdstore/reloadInfo")
    @ResponseBody
    public List<MDStoreInfo> reloadInfo(final ModelMap map) throws Exception {
        datasourceCache = null;
        return listMDStores(map);
    }

    @RequestMapping(value = "/ui/mdstore/getMdStoreInfo.do")
    @ResponseBody
    public MDStoreInfo getMdStoreInfo(final ModelMap map, @RequestParam(value = "id", required = true) final String profileId) throws Exception {

        final MDStoreInfo mdStoreInfo = retrieveMDStoreInfo(profileId);
        List<MDFormatDescription> indexFields = mdStoreUtils.getField(mdStoreInfo.getFormat(), mdStoreInfo.getLayout());
        List<String> idFields;
        if (indexFields != null) {
            idFields = indexFields.stream().map(MDFormatDescription::getName).collect(Collectors.toList());
            mdStoreInfo.setIndexFields(idFields);
        }
        return mdStoreInfo;
    }


    @RequestMapping(value = "/ui/mdstore/findRecords", method = RequestMethod.POST)
    @ResponseBody
    public MDStoreResult findRecords(final ModelMap map,
                                     @RequestBody final Map<String, String> queryParam) throws Exception {

        if (!queryParam.containsKey("mdId")) throw new MDStoreUIException("mdId parameters expected");

        final String mdId = queryParam.get("mdId");

        int offset = 0;

        if (queryParam.containsKey("page")) {
            try {
                offset = Integer.parseInt(queryParam.get("page"));
                queryParam.remove("page");
            } catch (Exception e) {
                log.error("Error on parsing page " + queryParam.get("page"));
            }
        }
        queryParam.remove("mdId");

        final MDStoreResult res = new MDStoreResult();
        ClassPathResource cpr = new ClassPathResource(recordXsltClasspath);
        ApplyXslt xslt = new ApplyXslt(cpr);
        res.setResult(mdStoreService.getMDStoreRecords(mdId, 10, offset, queryParam).stream().map(xslt::evaluate).collect(Collectors.toList()));


        //TERRIBLE HACK SHAME SANDRO SHAME!
        int count = 0;
        try {
            count = Integer.parseInt(queryParam.get("count"));
        } catch (Exception e) {
            log.error(e);
        }

        res.setCount(count);

        return res;
    }

    @RequestMapping(value = "/ui/mdstore/mdStoreResults")
    @ResponseBody
    public MDStoreResult getMDStoreResults(final ModelMap map,
                                           @RequestParam(value = "mdId", required = true) final String mdId,
                                           @RequestParam(value = "from", required = true) final int from,
                                           @RequestParam(value = "id_query", required = false) final String idToQuery,
                                           @RequestParam(value = "free_query", required = false) final String freeQuery) throws Exception {

        MDStoreService mdService = serviceLocator.getService(MDStoreService.class, mdId);
        ClassPathResource cpr = new ClassPathResource(recordXsltClasspath);
        ApplyXslt xslt = new ApplyXslt(cpr);
        MDStoreResult resultValues = new MDStoreResult();
        resultValues.setCount(getMdStoreInfo(map, mdId).getSize());

        if (idToQuery != null && !idToQuery.isEmpty()) {
            try {
                String record = mdService.deliverRecord(mdId, idToQuery);
                resultValues.setResult(Lists.newArrayList(xslt.evaluate(record)));
                return resultValues;
            } catch (DocumentNotFoundException e) {
                return null;
            }
        }


        W3CEndpointReference epr = mdService.deliverMDRecords(mdId, null, null, null);
        Iterable<String> result = resultSetClientFactory.getClient(epr);
        List<String> convertedList = new ArrayList<>();
        Iterator<String> it = result.iterator();
        int currentCounter = 0;
        while (currentCounter < from) {
            if (it.hasNext()) {
                it.next();
                currentCounter++;
            } else {
                resultValues.setResult(convertedList);
                return resultValues;
            }
        }

        for (int i = 0; i < 10; i++) {
            if (it.hasNext()) {
                convertedList.add(xslt.evaluate(it.next()));
//                convertedList.add(it.next());
            } else {
                break;
            }
        }
        resultValues.setResult(convertedList);
        return resultValues;
    }


    private Map<String, Info> datasourceFromMdStore() {
        final String query = "for $x in collection('/db/DRIVER/WorkflowDSResources/WorkflowDSResourceType') where $x[.//PARAM/@category/string()='MDSTORE_ID'] " +
                "return concat($x//PARAM[./@name='providerId'][1]/string(), '<::>',$x//PARAM[./@name='providerName'][1]/string(), '<::>', " +
                "string-join($x//PARAM[./@category/string()='MDSTORE_ID']/string(), '--'))";
        final ISLookUpService lookupService = serviceLocator.getService(ISLookUpService.class);
        try {
            Map<String, Info> result = Maps.newHashMap();
            List<String> results = lookupService.quickSearchProfile(query);
            for (String item : results) {
                String[] splitted = item.split("<::>");
                if (splitted != null && splitted.length == 3) {
                    final Info currentInfo = new Info();
                    currentInfo.setDatasourceId(splitted[0]);
                    currentInfo.setDatasourceName(splitted[1]);
                    final String[] mdstores = splitted[2].split("--");
                    if (mdstores != null) {
                        for (String mdstore : mdstores) {
                            result.put(mdstore, currentInfo);
                        }
                    }
                }
            }
            return result;
        } catch (ISLookUpException e) {
            log.error(e);
            return null;
        }
    }

    private MDStoreInfo retrieveMDStoreInfo(final String identifier) {

        MDStoreInfo result = null;
        try {
            result = null;
            final String xquery = " for $x in collection('/db/DRIVER/MDStoreDSResources/MDStoreDSResourceType') "
                    + " where $x//RESOURCE_IDENTIFIER/@value/string()=\"%s\"   return concat($x//RESOURCE_URI/@value/string() "
                    + ", '@::@',$x//RESOURCE_IDENTIFIER/@value/string() ,'@::@',$x//METADATA_FORMAT,'(-)',$x//METADATA_FORMAT_LAYOUT,  "
                    + " '(-)', $x//METADATA_FORMAT_INTERPRETATION ,'@::@',$x//LAST_STORAGE_DATE,'@::@',$x//NUMBER_OF_RECORDS)";

            List<String> results = serviceLocator.getService(ISLookUpService.class).quickSearchProfile(String.format(xquery, identifier));
            if (results != null && !results.isEmpty()) {
                result = MDStoreInfo.fromXqueryResult(results.get(0));
            }
        } catch (ISLookUpException e) {
            log.error(String.format("Error on getting information for mdstore with identifier: %s", identifier), e);
        }

        return result;
    }


    private Map<String, String> findDatasourceNameAssociatedToMdStore(final String mdStoreId) throws ISLookUpException {
        final String baseQuery = "for $x in collection('/db/DRIVER/WorkflowDSResources/WorkflowDSResourceType') where $x[.//PARAM/string()='%s'] return concat($x//PARAM[./@name='providerId'][1]/string(), '<::>',$x//PARAM[./@name='providerName'][1]/string())";
        final ISLookUpService lookupService = serviceLocator.getService(ISLookUpService.class);
        List<String> result = lookupService.quickSearchProfile(String.format(baseQuery, mdStoreId));
        Map<String, String> datasources = Maps.newHashMap();
        for (String item : result) {
            String[] splitted = item.split("<::>");
            if (splitted != null && splitted.length == 2) {
                datasources.put(splitted[0], splitted[1]);
            }
        }
        return datasources;
    }


    class Info {
        private String datasourceId;
        private String datasourceName;
        private String mdStoreId;


        public String getDatasourceName() {
            return datasourceName;
        }

        public void setDatasourceName(String datasourceName) {
            this.datasourceName = datasourceName;
        }

        public String getDatasourceId() {
            return datasourceId;
        }

        public void setDatasourceId(String datasourceId) {
            this.datasourceId = datasourceId;
        }

        public String getMdStoreId() {
            return mdStoreId;
        }

        public void setMdStoreId(String mdStoreId) {
            this.mdStoreId = mdStoreId;
        }
    }
}
