/**
 * Copyright (c) 2016-2017, Mihai Emil Andronache
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *  1)Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  2)Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *  3)Neither the name of charles-rest nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package com.amihaiemil.charles.aws;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.amazonaws.AmazonServiceException;
import com.amazonaws.http.HttpResponse;
import com.amihaiemil.charles.DataExportException;
import com.amihaiemil.charles.WebPage;
import com.amihaiemil.charles.aws.requests.AwsDelete;
import com.amihaiemil.charles.aws.requests.AwsHead;
import com.amihaiemil.charles.aws.requests.AwsHttpHeaders;
import com.amihaiemil.charles.aws.requests.AwsHttpRequest;
import com.amihaiemil.charles.aws.requests.AwsPost;
import com.amihaiemil.charles.aws.requests.EsHttpRequest;
import com.amihaiemil.charles.aws.requests.SignedRequest;
import com.amihaiemil.charles.rest.model.SearchResultsPage;

/**
 * AWS ElasticSearch repository that sends the webpages to the
 * Amazon ElasticSearch service, using the AWS sdk (for ease of request signing)
 * @author Mihai Andronache ([email protected])
 * @version $Id: e9ecb062678e95affea651c89b4cc3a8b081de87 $
 * @since 1.0.0
 * @todo #250:1h Implement the delete page knowledge. We have the repo method and unit
 *  test for it. Continue with the language, knowledge and steps.
 */
public final class AmazonElasticSearch implements ElasticSearch {
    private static final Logger LOG = LoggerFactory.getLogger(AmazonElasticSearch.class);    

    /**
     * Name of the Es index where the pages will be exported.
     */
    private String indexName;

    /**
     * AWS access key.
     */
    private AccessKeyId accesskey;
    
    /**
     * Aws secret key;
     */
    private SecretKey secretKey;
    
    /**
     * Aws ES region.
     */
    private Region reg;
    
    /**
     * ElasticSearch URL.
     */
    private EsEndPoint esEdp;
    
    /**
     * Ctor. 
     * @param indexName Name of the index.
     */
    public AmazonElasticSearch(final String indexName) {
        this(
            indexName,
            new StAccessKeyId(),
            new StSecretKey(),
            new StRegion(),
            new StEsEndPoint()
        );
    }
    
    /**
     * ctor.
     * @param indexName Name of the Es index where the pages will be exported.
     * @param accesskey Aws access key.
     * @param secretKey Aws secret key.
     * @param reg AWS ElasticSearch region.
     * @param es ElasticSearch URL.
     */
    public AmazonElasticSearch(
        final String indexName,
        final AccessKeyId accesskey,
        final SecretKey secretKey,
        final Region reg,
        final EsEndPoint es
    ) {
        this.indexName = indexName;
        this.accesskey = accesskey;
        this.secretKey = secretKey;
        this.reg = reg;
        this.esEdp = es;
    }

    @Override
    public SearchResultsPage search(final SearchQuery query) {
        final Map<String, String> headers = new HashMap<String, String>();
        headers.put("Content-Type", "application/json");
        AwsHttpRequest<SearchResultsPage> search =
            new SignedRequest<>(
                new AwsHttpHeaders<>(
                    new AwsPost<>(
                        new EsHttpRequest<>(
                            this.esEdp,
                            this.indexName + "/_search",
                            new SearchResponseHandler(),
                            new SimpleAwsErrorHandler(false)
                        ),
                        new ByteArrayInputStream(query.toJson().toString().getBytes())
                    ), headers
                ),
                this.accesskey,
                this.secretKey,
                this.reg
            );
        return search.perform();
    }
    
    @Override
    public void export(List<WebPage> pages) throws DataExportException {
        try {
            String data = new EsBulkJson(this.indexName, pages).structure();
            Map<String, String> headers = new HashMap<String, String>();
            headers.put("Content-Type", "application/json");
            final AwsHttpRequest<HttpResponse> index =
                new SignedRequest<>(
                    new AwsHttpHeaders<>(
                        new AwsPost<>(
                            new EsHttpRequest<>(
                                this.esEdp,
                                "_bulk",
                                new SimpleAwsResponseHandler(false),
                                new SimpleAwsErrorHandler(false)
                            ),
                            new ByteArrayInputStream(data.getBytes())
                        ), headers
                     ),
                     this.accesskey,
                     this.secretKey,
                     this.reg
                );
                index.perform();
        } catch (IOException e) {
            LOG.error(e.getMessage(), e);
            throw new DataExportException(e.getMessage());
        }
    }

    @Override
    public boolean exists() {
        final AwsHttpRequest<Boolean> head =
            new SignedRequest<>(
                new AwsHead<>(
                    new EsHttpRequest<>(
                        this.esEdp,
                        this.indexName,
                        new BooleanAwsResponseHandler(),
                        new SimpleAwsErrorHandler(false)
                    )
                ),
                this.accesskey,
                this.secretKey,
                this.reg
            );
        boolean exists = false;
        try {
            exists = head.perform();
        } catch (AmazonServiceException ex) {
            if (!(ex.getStatusCode() == HttpStatus.SC_NOT_FOUND)) {
                throw ex;
            }
        }
        return exists;
    }
    
    @Override
    public void delete() {
        final AwsHttpRequest<HttpResponse> deleteIndex =
            new SignedRequest<>(
                new AwsDelete<>(
                    new EsHttpRequest<>(
                        this.esEdp,
                        this.indexName,
                        new SimpleAwsResponseHandler(false),
                        new SimpleAwsErrorHandler(false)
                    )
                ),
                this.accesskey,
                this.secretKey,
                this.reg
           );
       deleteIndex.perform();
    }

    @Override
    public void delete(final String type, final String id) {
        final AwsHttpRequest<HttpResponse> deleteDoc =
            new SignedRequest<>(
                new AwsDelete<>(
                    new EsHttpRequest<>(
                        this.esEdp,
                        this.indexName + "/" + type + "/" + id,
                        new SimpleAwsResponseHandler(false),
                        new SimpleAwsErrorHandler(false)
                    )
                ),
                this.accesskey,
                this.secretKey,
                this.reg
           );
           deleteDoc.perform();
    }
    
}