/*
 * #%L
 * dev-eth0.de
 * %%
 * Copyright (C) 2016 dev-eth0.de
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package de.dev.eth0.elasticsearch.aem.indexing;

import com.day.cq.replication.AgentConfig;
import com.day.cq.replication.ReplicationActionType;
import com.day.cq.replication.ReplicationContent;
import com.day.cq.replication.ReplicationException;
import com.day.cq.replication.ReplicationLog;
import com.day.cq.replication.ReplicationResult;
import com.day.cq.replication.ReplicationTransaction;
import com.day.cq.replication.TransportContext;
import com.day.cq.replication.TransportHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.dev.eth0.elasticsearch.aem.service.ElasticSearchService;
import java.io.IOException;
import java.util.Collections;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.nio.entity.NStringEntity;
import org.apache.http.util.EntityUtils;
import org.apache.sling.commons.json.JSONException;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestClient;
import org.osgi.framework.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * TransportHandler Implementation that posts a ReplicationContent created by {@link ElasticSearchIndexContentBuilder} to a configured ElasticSearch.
 */
@Service(TransportHandler.class)
@Component(label = "Elastic Search Index Agent", immediate = true)
@Properties(
        @Property(name = Constants.SERVICE_RANKING, intValue = 1000))
public class ElasticSearchTransportHandler implements TransportHandler {

  @Reference
  protected ElasticSearchService elasticSearchService;

  private static final Logger LOG = LoggerFactory.getLogger(ElasticSearchTransportHandler.class);

  /**
   *
   * @param config
   * @return only accept if the serializationType is "elastic"
   */
  @Override
  public boolean canHandle(AgentConfig config) {
    return StringUtils.equalsIgnoreCase(config.getSerializationType(), ElasticSearchIndexContentBuilder.NAME);
  }

  /**
   *
   * @param ctx
   * @param tx
   * @return
   * @throws ReplicationException
   */
  @Override
  public ReplicationResult deliver(TransportContext ctx, ReplicationTransaction tx) throws ReplicationException {
    ReplicationLog log = tx.getLog();
    try {
      RestClient restClient = elasticSearchService.getRestClient();
      ReplicationActionType replicationType = tx.getAction().getType();
      if (replicationType == ReplicationActionType.TEST) {
        return doTest(ctx, tx, restClient);
      }
      else {
        log.info(getClass().getSimpleName() + ": ---------------------------------------");
        if (tx.getContent() == ReplicationContent.VOID) {
          LOG.warn("No Replication Content provided");
          return new ReplicationResult(true, 0, "No Replication Content provided for path " + tx.getAction().getPath());
        }
        switch (replicationType) {
          case ACTIVATE:
            return doActivate(ctx, tx, restClient);
          case DEACTIVATE:
            return doDeactivate(ctx, tx, restClient);
          default:
            log.warn(getClass().getSimpleName() + ": Replication action type" + replicationType + " not supported.");
            throw new ReplicationException("Replication action type " + replicationType + " not supported.");
        }
      }
    }
    catch (JSONException jex) {
      LOG.error("JSON was invalid", jex);
      return new ReplicationResult(false, 0, jex.getLocalizedMessage());
    }
    catch (IOException ioe) {
      log.error(getClass().getSimpleName() + ": Could not perform Indexing due to " + ioe.getLocalizedMessage());
      LOG.error("Could not perform Indexing", ioe);
      return new ReplicationResult(false, 0, ioe.getLocalizedMessage());
    }
  }

  private ReplicationResult doDeactivate(TransportContext ctx, ReplicationTransaction tx, RestClient restClient) throws ReplicationException, JSONException, IOException {
    ReplicationLog log = tx.getLog();

    ObjectMapper mapper = new ObjectMapper();
    IndexEntry content = mapper.readValue(tx.getContent().getInputStream(), IndexEntry.class);
    Response deleteResponse = restClient.performRequest(
            "DELETE",
            "/" + content.getIndex() + "/" + content.getType() + "/" + DigestUtils.md5Hex(content.getPath()),
            Collections.<String, String>emptyMap());
    LOG.debug(deleteResponse.toString());
    log.info(getClass().getSimpleName() + ": Delete Call returned " + deleteResponse.getStatusLine().getStatusCode() + ": " + deleteResponse.getStatusLine().getReasonPhrase());
    if (deleteResponse.getStatusLine().getStatusCode() == HttpStatus.SC_CREATED || deleteResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
      return ReplicationResult.OK;
    }
    LOG.error("Could not delete " + content.getType() + " at " + content.getPath());
    return new ReplicationResult(false, 0, "Replication failed");
  }

  /**
   * Perform the replication. All logic is covered in {@link ElasticSearchIndexContentBuilder} so we only need to transmit the JSON to ElasticSearch
   *
   * @param ctx
   * @param tx
   * @param restClient
   * @return
   * @throws ReplicationException
   */
  private ReplicationResult doActivate(TransportContext ctx, ReplicationTransaction tx, RestClient restClient) throws ReplicationException, JSONException, IOException {
    ReplicationLog log = tx.getLog();
    ObjectMapper mapper = new ObjectMapper();
    IndexEntry content = mapper.readValue(tx.getContent().getInputStream(), IndexEntry.class);
    if (content != null) {
      log.info(getClass().getSimpleName() + ": Indexing " + content.getPath());
      String contentString = mapper.writeValueAsString(content.getContent());
      log.debug(getClass().getSimpleName() + ": Index-Content: " + contentString);
      LOG.debug("Index-Content: " + contentString);

      HttpEntity entity = new NStringEntity(contentString, "UTF-8");
      Response indexResponse = restClient.performRequest(
              "PUT",
              "/" + content.getIndex() + "/" + content.getType() + "/" + DigestUtils.md5Hex(content.getPath()),
              Collections.<String, String>emptyMap(),
              entity);
      LOG.debug(indexResponse.toString());
      log.info(getClass().getSimpleName() + ": " + indexResponse.getStatusLine().getStatusCode() + ": " + indexResponse.getStatusLine().getReasonPhrase());
      if (indexResponse.getStatusLine().getStatusCode() == HttpStatus.SC_CREATED || indexResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
        return ReplicationResult.OK;
      }
    }
    LOG.error("Could not replicate");
    return new ReplicationResult(false, 0, "Replication failed");
  }

  /**
   * Test Connection to ElasticSearch by getting basic informations
   *
   * @param ctx
   * @param tx
   * @param restClient
   * @return
   * @throws ReplicationException
   */
  private ReplicationResult doTest(TransportContext ctx, ReplicationTransaction tx, RestClient restClient) throws ReplicationException, IOException {
    ReplicationLog log = tx.getLog();
    Response response = restClient.performRequest("GET", "/", Collections.singletonMap("pretty", "true"));
    log.info(getClass().getSimpleName() + ": ---------------------------------------");
    log.info(getClass().getSimpleName() + ": " + response.toString());
    log.info(getClass().getSimpleName() + ": " + EntityUtils.toString(response.getEntity()));
    if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
      return ReplicationResult.OK;
    }
    return new ReplicationResult(false, 0, "Replication test failed");
  }

}