package com.azuredemo.core.SaveAndFetchFromAzure.impl;

import com.azuredemo.core.SaveAndFetchFromAzure.StoreAndFetchDataFromAzureStorage;
import com.azuredemo.core.configuration.service.AzurePortalConfigurationService;
import com.day.cq.search.PredicateGroup;
import com.day.cq.search.Query;
import com.day.cq.search.QueryBuilder;
import com.day.cq.search.result.Hit;
import com.day.cq.search.result.SearchResult;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;


@Component(service= StoreAndFetchDataFromAzureStorage.class,immediate=true)
public class SaveAndFetchFromAzure implements com.azuredemo.core.SaveAndFetchFromAzure.StoreAndFetchDataFromAzureStorage{
    @Reference
    AzurePortalConfigurationService azurePortalConfigurationService;

    @Reference
    QueryBuilder queryBuilder;

    Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    //Code the save submitted form data in Azure and return the Blob Id
    public String saveFormDatainAzure(String formData,String contentType,String metaDataTags) {
        String sasToken = azurePortalConfigurationService.getSASToken();
        String storageURI = azurePortalConfigurationService.getStorageURI();
        logger.debug("The SAS Token is "+sasToken);
        logger.debug("The Storage URL is "+storageURI);
        logger.debug("The form data is "+formData);
        int timeout = 5;
        RequestConfig config = RequestConfig.custom()
                .setConnectTimeout(timeout * 1000)
                .setConnectionRequestTimeout(timeout)
                .setSocketTimeout(timeout * 1000).build();
        org.apache.http.impl.client.CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(config).build();
        UUID uuid = UUID.randomUUID();
        String putRequestURL = storageURI+uuid.toString();
        putRequestURL = putRequestURL+sasToken;
        logger.debug("The put Request "+putRequestURL);
        HttpPut httpPut = new HttpPut(putRequestURL);
        httpPut.addHeader("x-ms-blob-type","BlockBlob");
        //httpPut.addHeader("Content-Type","text/plain");
        httpPut.addHeader("Content-Type",contentType);
        httpPut.addHeader("x-ms-tags",metaDataTags);

        try
        {
            httpPut.setEntity(new StringEntity(formData));
            CloseableHttpResponse response = httpClient.execute(httpPut);
            logger.debug("Response code "+response.getStatusLine().getStatusCode());
            System.out.println("The response code is "+response.getStatusLine().getStatusCode()+"uuid "+uuid.toString());
            if(response.getStatusLine().getStatusCode() == 201)
            {
                return uuid.toString();
            }
        }
        catch (IOException e)
        {
            logger.error("Error: "+e.getMessage());
            throw new RuntimeException(e);
        }
        return null;
    }

    @Override
    public String saveFormDatainAzure(String formData, String contentType) {
        String sasToken = azurePortalConfigurationService.getSASToken();
        String storageURI = azurePortalConfigurationService.getStorageURI();
        logger.debug("The SAS Token is "+sasToken);
        logger.debug("The Storage URL is "+storageURI);
        logger.debug("The form data is "+formData);
        int timeout = 5;
        RequestConfig config = RequestConfig.custom()
                .setConnectTimeout(timeout * 1000)
                .setConnectionRequestTimeout(timeout)
                .setSocketTimeout(timeout * 1000).build();
        org.apache.http.impl.client.CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(config).build();
        UUID uuid = UUID.randomUUID();
        String putRequestURL = storageURI+uuid.toString();
        putRequestURL = putRequestURL+sasToken;
        logger.debug("The put Request "+putRequestURL);
        HttpPut httpPut = new HttpPut(putRequestURL);
        httpPut.addHeader("x-ms-blob-type","BlockBlob");
        httpPut.addHeader("Content-Type",contentType);
        try
        {
            httpPut.setEntity(new StringEntity(formData));
            CloseableHttpResponse response = httpClient.execute(httpPut);
            logger.debug("Response code "+response.getStatusLine().getStatusCode());
            System.out.println("The response code is "+response.getStatusLine().getStatusCode()+"uuid "+uuid.toString());
            if(response.getStatusLine().getStatusCode() == 201)
            {
                return uuid.toString();
            }
        }
        catch (IOException e)
        {
            logger.error("Error: "+e.getMessage());
            throw new RuntimeException(e);
        }
        return null;

    }

    @Override
    // Save the submitted form attachment in azure and return its url
    public String saveFormAttachmentinAzure(InputStream attachmentStream,String fileName, String contentType) {
        logger.debug("Saving "+fileName+"  form attachment in azure  ");
        String sasToken = azurePortalConfigurationService.getSASToken();
        String storageURI = azurePortalConfigurationService.getStorageURI();
        logger.debug("The SAS Token is "+sasToken);
        logger.debug("The Storage URL is "+storageURI);

        int timeout = 5;
        RequestConfig config = RequestConfig.custom()
                .setConnectTimeout(timeout * 1000)
                .setConnectionRequestTimeout(timeout)
                .setSocketTimeout(timeout * 1000).build();
        org.apache.http.impl.client.CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(config).build();
        UUID uuid = UUID.randomUUID();
        String putRequestURL = storageURI+uuid.toString();
        putRequestURL = putRequestURL+sasToken;
        System.out.println("The put Request "+putRequestURL);
        HttpPut httpPut = new HttpPut(putRequestURL);
        httpPut.addHeader("x-ms-blob-type","BlockBlob");

        try
        {

            InputStreamEntity ies = new InputStreamEntity(attachmentStream,attachmentStream.available());

            httpPut.setEntity(ies);

            CloseableHttpResponse response = httpClient.execute(httpPut);
            logger.debug("Response code    "+response.getStatusLine().getStatusCode());
            logger.debug("Added file attachment  "+ response.getStatusLine().getStatusCode()+"  uuid "+uuid.toString()+"   "+response.getStatusLine().getReasonPhrase());
            if(response.getStatusLine().getStatusCode() == 201)
            {
                return putRequestURL;
            }
        }
        catch (Exception e)
        {

            logger.error("Error: "+e.getMessage());
            throw new RuntimeException(e);
        }
        return null;
    }

    @Override
    // Fetch the blob from Azure using the Blob Id
    public String getBlobData(String blobID) {
        String sasToken = azurePortalConfigurationService.getSASToken();
        String storageURI = azurePortalConfigurationService.getStorageURI();
        logger.debug("The SAS Token is "+sasToken);
        logger.debug("The Storage URL is "+storageURI);
        int timeout = 5;
        RequestConfig config = RequestConfig.custom()
                .setConnectTimeout(timeout * 1000)
                .setConnectionRequestTimeout(timeout)
                .setSocketTimeout(timeout * 1000).build();
        org.apache.http.impl.client.CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(config).build();
        String getRequestURL = storageURI+blobID;
        HttpGet httpGet =  new HttpGet(getRequestURL+sasToken);
        logger.debug("The url is:  "+getRequestURL+sasToken);
        try {
            CloseableHttpResponse response = httpClient.execute(httpGet);
            HttpEntity httpEntity = response.getEntity();
            String rawResponse = EntityUtils.toString(httpEntity);
            logger.debug("The Form Data  is " + rawResponse);
            return rawResponse;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }


    }

    @Override
    // Get the meta data tags associated with the submitted form name
    public String getMetaDataTags(String submittedFormName,String formPath,Session session,String formData) {

        JsonObject jsonObject = JsonParser.parseString(formData).getAsJsonObject();
        List<String>metaDataTags = new ArrayList<String>();
        metaDataTags.add("formName="+submittedFormName);
        Map< String, String > map = new HashMap< String, String >();
        map.put("path", formPath);
        map.put("1_property", "Searchable");
        map.put("1_property.value","true");
        Query query = queryBuilder.createQuery(PredicateGroup.create(map),session);
        query.setStart(0);
        query.setHitsPerPage(20);
        SearchResult result = query.getResult();
        logger.debug("Get result hits " + result.getHits().size());
        for (Hit hit: result.getHits()) {
            try {
                    logger.debug(hit.getPath());
                    String jsonElementName = (String) hit.getProperties().get("name");
                    String fieldName = hit.getProperties().get("name").toString();
                    if(jsonObject.has(jsonElementName)) {
                        if (jsonObject.get(jsonElementName).isJsonArray()) {
                            System.out.println("$$$$$ got array values  " + jsonElementName);
                            JsonArray arrayOfValues = jsonObject.get(jsonElementName).getAsJsonArray();
                            StringBuilder valuesString = new StringBuilder();
                            for (int j = 0; j < arrayOfValues.size(); j++) {
                                valuesString.append(arrayOfValues.get(j).getAsString());
                                if (j < arrayOfValues.size() - 1) {
                                    valuesString.append(" and ");
                                }
                            }
                            metaDataTags.add(fieldName + "=" + valuesString.toString());
                        } else {
                            logger.debug("The searchable field name is " + fieldName + "the json element name is " + jsonElementName);
                            metaDataTags.add(fieldName + "=" + jsonObject.get(jsonElementName).getAsString());
                        }
                    }

            } catch (RepositoryException e) {
                throw new RuntimeException(e);
            }
        }
        return String.join("&",metaDataTags);
    }

    @Override
    // Fetch the fields marked as searchable for a specific form
    public String getSearchableFields(String formPath, Session session) {
        Map< String, String > map = new HashMap< String, String >();
        map.put("path", formPath);
        map.put("1_property", "Searchable");
        map.put("1_property.value","true");
        Query query = queryBuilder.createQuery(PredicateGroup.create(map),session);
        query.setStart(0);
        query.setHitsPerPage(20);
        SearchResult result = query.getResult();
        logger.debug("Get result hits " + result.getHits().size());

        JsonArray arrayOfSearchableFields = new JsonArray();

        for (Hit hit: result.getHits()) {
            try {
                    JsonObject searchableField = new JsonObject();
                    logger.debug("The path of the searchable field is "+hit.getPath());
                    searchableField.addProperty("fieldName",hit.getProperties().get("jcr:title").toString());

                    searchableField.addProperty("fieldPath",hit.getProperties().get("name").toString());
                    arrayOfSearchableFields.add(searchableField);


            } catch (RepositoryException e) {
                throw new RuntimeException(e);
            }
        }

        return arrayOfSearchableFields.toString();
    }

    @Override
    public String getFormsList(String folderPath, Session session) {
        Map< String, String > map = new HashMap< String, String >();
        map.put("path", folderPath);
        map.put("1_property", "jcr:primaryType");
        map.put("1_property.value","cq:Page");
        map.put("2_property","jcr:content/sling:resourceType");
        map.put("2_property.value","azuredemo/components/adaptiveForm/page");

        Query query = queryBuilder.createQuery(PredicateGroup.create(map),session);
        query.setStart(0);
        query.setHitsPerPage(20);
        SearchResult result = query.getResult();
        logger.debug("Get result hits " + result.getHits().size());
        System.out.println("Get result hits " + result.getHits().size());
        JsonArray formNamesArray = new JsonArray();
        for (Hit hit: result.getHits()) {
            try {
                JsonObject formObject = new JsonObject();
                System.out.println("The path of the  form  is " + hit.getPath()+"/jcr:content");
                formObject.addProperty("formTitle",hit.getTitle());
                formObject.addProperty("formPath",hit.getPath()+"/jcr:content");
                formNamesArray.add(formObject);

                System.out.println("The form name is  "+hit.getTitle());
            } catch (RepositoryException e) {
                throw new RuntimeException(e);
            }
        }


                return formNamesArray.toString();
    }

    @Override
    // Fetch the options for a dropdown/choice group field
    public String getOptions(String formPath, String fieldName, Session session)
    {
        Map< String, String > map = new HashMap< String, String >();
        map.put("path", formPath);
        map.put("1_property", "Searchable");
        map.put("1_property.value","true");
        map.put("2_property", "name");
        map.put("2_property.value",fieldName);
        Query query = queryBuilder.createQuery(PredicateGroup.create(map),session);
        query.setStart(0);
        query.setHitsPerPage(20);
        SearchResult result = query.getResult();
        logger.debug("Get result hits " + result.getHits().size());
        logger.debug("Getting options for  "+formPath+"field name "+fieldName);
        javax.jcr.Node fieldNode = null;
        JsonArray dropDownValues = new JsonArray();
        try {
            fieldNode = session.getNode(result.getHits().get(0).getPath());
            Value[] enumNames= fieldNode.getProperty("enumNames").getValues();
            Value[] enumValues = fieldNode.getProperty("enum").getValues();
            for(int i=0;i<enumValues.length;i++) {
                JsonObject dropDownValue = new JsonObject();
                dropDownValue.addProperty("name", String.valueOf(enumNames[i]));
                dropDownValue.addProperty("value", String.valueOf(enumValues[i]));
                dropDownValues.add(dropDownValue);
            }

        } catch (RepositoryException e) {
            throw new RuntimeException(e);
        }
        return dropDownValues.toString();


    }




    public JsonObject getMetaDataTags(String blobID,JsonObject blob)
    {
        String sasToken = azurePortalConfigurationService.getSASToken();
        String storageURI = azurePortalConfigurationService.getStorageURI();
        logger.debug("The SAS Token is "+sasToken);
        logger.debug("The Storage URL is "+storageURI);
        int timeout = 5;
        RequestConfig config = RequestConfig.custom()
                .setConnectTimeout(timeout * 1000)
                .setConnectionRequestTimeout(timeout)
                .setSocketTimeout(timeout * 1000).build();
        org.apache.http.impl.client.CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(config).build();
        HttpGet httpGet =  new HttpGet(storageURI+blobID+sasToken+"&comp=tags");
        DocumentBuilderFactory dbf  = DocumentBuilderFactory.newInstance();
        try {
            CloseableHttpResponse response = httpClient.execute(httpGet);
            HttpEntity httpEntity = response.getEntity();
            String xmlString = EntityUtils.toString(httpEntity);
            InputStream targetStream = new ByteArrayInputStream(xmlString.getBytes());
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document xmlDocument = db.parse(targetStream);
            XPath xPath = XPathFactory.newInstance().newXPath();
            String tagName = "//Tags/TagSet/Tag/Key/text()";
            String tagValue = "//Tags/TagSet/Tag/Value/text()";
            NodeList listOfTagNames = (NodeList) xPath.compile(tagName).evaluate(xmlDocument, XPathConstants.NODESET);
            NodeList listOfTagValues = (NodeList) xPath.compile(tagValue).evaluate(xmlDocument, XPathConstants.NODESET);

            //org.w3c.dom.NodeList tags = doc.getElementsByTagName("Tag");
            for(int i=0;i<listOfTagNames.getLength();i++)
            {
                logger.debug("The tag name is "+listOfTagNames.item(i).getNodeValue());
                logger.debug("The tag value is "+listOfTagValues.item(i).getNodeValue());
                blob.addProperty(listOfTagNames.item(i).getNodeValue(),listOfTagValues.item(i).getNodeValue());
            }
        } catch (ParserConfigurationException | SAXException | IOException | XPathExpressionException e) {
            throw new RuntimeException(e);
        }

        return blob;

    }

    @Override
    // Fetch blobs from Azure using the specified search criteria
    public String getBlobs(String metaDataQuery) {
        int timeout = 5;
        RequestConfig config = RequestConfig.custom()
                .setConnectTimeout(timeout * 1000)
                .setConnectionRequestTimeout(timeout)
                .setSocketTimeout(timeout * 1000).build();
        org.apache.http.impl.client.CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(config).build();

        String sasToken = azurePortalConfigurationService.getSASToken();
        String storageURI = azurePortalConfigurationService.getStorageURI();
        logger.debug("The SAS Token is "+sasToken);
        logger.debug("The Storage URL is "+storageURI);


        String whereCriteria = null;
        // whereCriteria = URLEncoder.encode(metaDataQuery,"utf-8");
        try {
            whereCriteria = URLEncoder.encode(metaDataQuery, StandardCharsets.UTF_8.toString()).replace("+", "%20");
            logger.debug("Before encoding  "+metaDataQuery);
            logger.debug("#####  "+whereCriteria);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }

        sasToken  = sasToken+"&restype=container&comp=blobs&where="+whereCriteria;

         HttpGet httpGet =  new HttpGet(storageURI+sasToken);
        JsonObject searchResults = new JsonObject();


        JsonArray blobsArray = new JsonArray();
        try {
            CloseableHttpResponse response = httpClient.execute(httpGet);
            HttpEntity httpEntity = response.getEntity();
            String xmlString = EntityUtils.toString(httpEntity);
            InputStream targetStream = new ByteArrayInputStream(xmlString.getBytes());
            DocumentBuilderFactory dbf  = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            XPath xPath = XPathFactory.newInstance().newXPath();
            String expression = "//Blobs/Blob/Name/text()";
            Document xmlDocument = db.parse(targetStream);
            org.w3c.dom.Node blob = xmlDocument.getElementsByTagName("Blob").item(0);
           NodeList listOfBlobs = (NodeList) xPath.compile(expression).evaluate(xmlDocument, XPathConstants.NODESET);
           for(int i=0;i<listOfBlobs.getLength();i++)
           {

              //String blobName = (String) xPath.compile("/Name").evaluate(blob1, XPathConstants.STRING);
              logger.debug("The blob name is "+listOfBlobs.item(i).getNodeValue());
              JsonObject jsonBlob = new JsonObject();
              jsonBlob.addProperty("blobID",listOfBlobs.item(i).getNodeValue());
              jsonBlob = getMetaDataTags(jsonBlob.get("blobID").getAsString(),jsonBlob);
              blobsArray.add(jsonBlob);
           }
           searchResults.add("blobs",blobsArray);
            logger.debug("The number of blobs I got was  "+listOfBlobs.getLength());

        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ParserConfigurationException e) {
            throw new RuntimeException(e);
        } catch (SAXException e) {
            throw new RuntimeException(e);
        } catch (XPathExpressionException e) {
            throw new RuntimeException(e);
        }

        return searchResults.toString();
    }
}
