package eu.eudat.logic.managers;

import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import eu.eudat.data.dao.criteria.DatasetProfileCriteria;
import eu.eudat.data.entities.DatasetProfile;
import eu.eudat.data.query.items.item.datasetprofile.DatasetProfileAutocompleteRequest;
import eu.eudat.data.query.items.table.datasetprofile.DatasetProfileTableRequestItem;
import eu.eudat.exceptions.datasetprofile.DatasetProfileNewVersionException;
import eu.eudat.logic.builders.model.models.DataTableDataBuilder;
import eu.eudat.logic.services.ApiContext;
import eu.eudat.logic.services.operations.DatabaseRepository;
import eu.eudat.logic.utilities.builders.XmlBuilder;
import eu.eudat.logic.utilities.documents.helpers.FileEnvelope;
import eu.eudat.logic.utilities.documents.xml.datasetProfileXml.ExportXmlBuilderDatasetProfile;
import eu.eudat.logic.utilities.documents.xml.datasetProfileXml.ImportXmlBuilderDatasetProfile;
import eu.eudat.models.data.components.commons.datafield.AutoCompleteData;
import eu.eudat.models.data.datasetprofile.DatasetProfileAutocompleteItem;
import eu.eudat.models.data.datasetprofile.DatasetProfileListingModel;
import eu.eudat.models.data.entities.xmlmodels.datasetprofiledefinition.Field;
import eu.eudat.models.data.externaldataset.ExternalAutocompleteFieldModel;
import eu.eudat.models.data.helpers.common.DataTableData;
import eu.eudat.queryable.QueryableList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.activation.MimetypesFileTypeMap;
import javax.xml.xpath.*;
import java.io.IOException;
import java.nio.file.Files;
import java.util.*;

import java.io.*;


@Component
public class DatasetProfileManager {
    private static final Logger logger = LoggerFactory.getLogger(DatasetProfileManager.class);

    private ApiContext apiContext;
    private DatabaseRepository databaseRepository;

    @Autowired
    public DatasetProfileManager(ApiContext apiContext) {
        this.apiContext = apiContext;
        this.databaseRepository = apiContext.getOperationsContext().getDatabaseRepository();
    }

    public eu.eudat.models.data.admin.composite.DatasetProfile getDatasetProfile(String id) {
        eu.eudat.data.entities.DatasetProfile profile = apiContext.getOperationsContext().getDatabaseRepository().getDatasetProfileDao().find(UUID.fromString(id));
        eu.eudat.models.data.admin.composite.DatasetProfile datasetprofile = AdminManager.generateDatasetProfileModel(profile);
        datasetprofile.setLabel(profile.getLabel());
        datasetprofile.setStatus(profile.getStatus());
        datasetprofile.setDescription(profile.getDescription());

        return datasetprofile;
    }

    public List<DatasetProfileAutocompleteItem> getWithCriteria(DatasetProfileAutocompleteRequest datasetProfileAutocompleteRequest) throws IllegalAccessException, InstantiationException {
        QueryableList<DatasetProfile> items = databaseRepository.getDatasetProfileDao().getWithCriteria(datasetProfileAutocompleteRequest.getCriteria());
        List<DatasetProfileAutocompleteItem> datasetProfiles = items.select(item -> new DatasetProfileAutocompleteItem().fromDataModel(item));
        return datasetProfiles;
    }

    public DatasetProfile clone(String id) {
        DatasetProfile profile = apiContext.getOperationsContext().getDatabaseRepository().getDatasetProfileDao().find(UUID.fromString(id));
        apiContext.getOperationsContext().getDatabaseRepository().detachEntity(profile);
        profile.setId(null);
        return profile;
    }

    public DataTableData<DatasetProfileListingModel> getPaged(DatasetProfileTableRequestItem datasetProfileTableRequestItem) throws Exception {
        QueryableList<DatasetProfile> items = apiContext.getOperationsContext().getDatabaseRepository().getDatasetProfileDao().getWithCriteria(datasetProfileTableRequestItem.getCriteria());
        QueryableList<DatasetProfile> pagedItems = PaginationManager.applyPaging(items, datasetProfileTableRequestItem);
        List<DatasetProfileListingModel> datasetProfiles = pagedItems.select(item -> new DatasetProfileListingModel().fromDataModel(item));
        return apiContext.getOperationsContext().getBuilderFactory().getBuilder(DataTableDataBuilder.class).data(datasetProfiles).totalCount(items.count()).build();
    }

    public List<DatasetProfileListingModel> getAll(DatasetProfileTableRequestItem tableRequestItem) throws IllegalAccessException, InstantiationException {
        QueryableList<DatasetProfile> items = databaseRepository.getDatasetProfileDao().getWithCriteria(tableRequestItem.getCriteria());
        List<DatasetProfileListingModel> datasetProfiles = items.select(item -> new DatasetProfileListingModel().fromDataModel(item));
        return datasetProfiles;
    }

    public eu.eudat.models.data.entities.xmlmodels.datasetprofiledefinition.Field queryForField(String xml, String fieldId) throws XPathExpressionException {
        eu.eudat.models.data.entities.xmlmodels.datasetprofiledefinition.Field field = new Field();
        Document document = XmlBuilder.fromXml(xml);
        XPathFactory xpathFactory = XPathFactory.newInstance();
        XPath xpath = xpathFactory.newXPath();
        XPathExpression expr =
                xpath.compile("//field[@id='" + fieldId + "']");
        Element name = (Element) expr.evaluate(document, XPathConstants.NODE);
        field.fromXml(name);
        return field;
    }

    public List<ExternalAutocompleteFieldModel> getAutocomplete(AutoCompleteData data, String like) {
        List<ExternalAutocompleteFieldModel> result = new LinkedList<>();
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setAccept(Collections.singletonList(MediaType.valueOf("application/vnd.api+json; charset=utf-8")));
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<>("parameters", headers);

        ResponseEntity<Object> response = restTemplate.exchange(data.getUrl() + "?search=" + like, HttpMethod.GET, entity, Object.class);
        DocumentContext jsonContext = JsonPath.parse(response.getBody());

        List<Map<String, String>> jsonItems = jsonContext.read(data.getOptionsRoot() + "['" + data.getAutoCompleteOptions().getLabel() + "','" + data.getAutoCompleteOptions().getValue() + "','" + data.getAutoCompleteOptions().getSource() + "']");
        jsonItems.forEach(item -> result.add(new ExternalAutocompleteFieldModel(item.get(data.getAutoCompleteOptions().getValue()), item.get(data.getAutoCompleteOptions().getLabel()), item.get(data.getAutoCompleteOptions().getSource()))));

        return result;
    }

    public ResponseEntity<byte[]> getDocument(eu.eudat.models.data.user.composite.DatasetProfile datasetProfile, String label) throws IllegalAccessException, IOException, InstantiationException {
        FileEnvelope envelope = getXmlDocument(datasetProfile, label);
        InputStream resource = new FileInputStream(envelope.getFile());
        logger.info("Mime Type of " + envelope.getFilename() + " is " +
                new MimetypesFileTypeMap().getContentType(envelope.getFile()));
        HttpHeaders responseHeaders = new HttpHeaders();
        responseHeaders.setContentLength(envelope.getFile().length());
        responseHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        responseHeaders.set("Content-Disposition", "attachment;filename=" + envelope.getFilename() + ".xml");
        responseHeaders.set("Access-Control-Expose-Headers", "Content-Disposition");
        responseHeaders.get("Access-Control-Expose-Headers").add("Content-Type");

        byte[] content = org.apache.poi.util.IOUtils.toByteArray(resource);
        resource.close();
        Files.deleteIfExists(envelope.getFile().toPath());

        return new ResponseEntity<>(content,
                responseHeaders,
                HttpStatus.OK);
    }

    public FileEnvelope getXmlDocument(eu.eudat.models.data.user.composite.DatasetProfile datatasetProfile, String label) throws InstantiationException, IllegalAccessException, IOException {
        ExportXmlBuilderDatasetProfile xmlBuilder = new ExportXmlBuilderDatasetProfile();
        File file = xmlBuilder.build(datatasetProfile);
        FileEnvelope fileEnvelope = new FileEnvelope();
        fileEnvelope.setFile(file);
        fileEnvelope.setFilename(label);
        return fileEnvelope;
    }

    public eu.eudat.logic.utilities.documents.xml.datasetProfileXml.datasetProfileModel.DatasetProfile createDatasetProfileFromXml(MultipartFile multiPartFile) {
        ImportXmlBuilderDatasetProfile xmlBuilder = new ImportXmlBuilderDatasetProfile();
        try {
            return xmlBuilder.build(convert(multiPartFile));
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    private static File convert(MultipartFile file) throws IOException {
        File convFile = new File(file.getOriginalFilename());
        convFile.createNewFile();
        FileOutputStream fos = new FileOutputStream(convFile);
        fos.write(file.getBytes());
        fos.close();
        return convFile;
    }

    public eu.eudat.data.entities.DatasetProfile createNewVersionDatasetProfile(String id, eu.eudat.models.data.admin.composite.DatasetProfile profile) throws Exception {
        // Getting the DatasetProfile which we will create its new version.
        eu.eudat.data.entities.DatasetProfile oldDatasetProfile = apiContext.getOperationsContext().getDatabaseRepository().getDatasetProfileDao().find(UUID.fromString(id));

        // Getting the DatasetProfile with the latest Version.
        DatasetProfileCriteria criteria = new DatasetProfileCriteria();
        LinkedList<UUID> list = new LinkedList<>();
        list.push(oldDatasetProfile.getGroupId());
        criteria.setGroupIds(list);
        criteria.setAllVersions(false);
        QueryableList<DatasetProfile> datasetProfileQueryableList = apiContext.getOperationsContext().getDatabaseRepository().getDatasetProfileDao().getWithCriteria(criteria);
        eu.eudat.data.entities.DatasetProfile latestVersionDatasetProfile = datasetProfileQueryableList.getSingle();

        if (latestVersionDatasetProfile.getVersion().equals(oldDatasetProfile.getVersion())){
            eu.eudat.models.data.admin.composite.DatasetProfile sortedProfile = profile.toShort();
            eu.eudat.data.entities.DatasetProfile modelDefinition = AdminManager.generateViewStyleDefinition(sortedProfile, apiContext);
            modelDefinition.setLabel(oldDatasetProfile.getLabel());
            modelDefinition.setVersion((short) (oldDatasetProfile.getVersion() + 1));
            modelDefinition.setGroupId(oldDatasetProfile.getGroupId());
            apiContext.getOperationsContext().getDatabaseRepository().getDatasetProfileDao().createOrUpdate(modelDefinition);
            return modelDefinition;
        } else {
            throw new DatasetProfileNewVersionException("Version to update not the latest.");
        }
    }
}
