/*
 * Copyright (c) 2016 The original author or authors
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution.
 *
 *      The Eclipse Public License is available at
 *      http://www.eclipse.org/legal/epl-v10.html
 *
 *      The Apache License v2.0 is available at
 *      http://www.opensource.org/licenses/apache2.0.php
 *
 * You may elect to redistribute this code under either of these licenses.
 */
package io.vertx.ext.consul.dc;

import com.pszymczyk.consul.ConsulProcess;
import com.pszymczyk.consul.ConsulStarterBuilder;
import com.pszymczyk.consul.LogLevel;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.net.ServerSocket;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collector;

/**
 * @author <a href="mailto:[email protected]">Ruslan Sennov</a>
 */
public class ConsulAgent {

  private final ConsulProcess process;
  private final String consulVersion;
  private final String masterToken;
  private final String host;
  private final String name;
  private final int httpsPort;

  ConsulAgent(ConsulDatacenter dc, ConsulAgentOptions options) {
    JsonObject cfg = new JsonObject()
      .put("server", true)
      .put("advertise_addr", options.getAddress())
      .put("client_addr", options.getAddress())
      .put("leave_on_terminate", true)
      .put("datacenter", dc.getName())
      .put("node_name", options.getNodeName())
      .put("node_id", options.getNodeId());
    if (options.getConsulVersion().compareTo("1.4.0") >= 0) {
      cfg.put("acl", new JsonObject()
          .put("enabled", true)
          .put("default_policy", "deny")
          .put("tokens", new JsonObject().put("master", dc.getMasterToken()))
        )
        .put("primary_datacenter", dc.getName());
    } else {
      cfg.put("acl_default_policy", "deny")
        .put("acl_master_token", dc.getMasterToken())
        .put("acl_datacenter", dc.getName());
    }
    if (options.getConsulVersion().compareTo("0.8.0") < 0) {
      cfg.put("ports", new JsonObject().put("rpc", getFreePort()));
    }
    if (options.getConsulVersion().compareTo("0.8.0") >= 0) {
      cfg.put("acl_enforce_version_8", false);
    }
    if (options.getConsulVersion().compareTo("0.9.0") >= 0) {
      cfg.put("enable_script_checks", true);
    }
    int sslCnt = 0;
    if (options.getKeyFile() != null) {
      cfg.put("key_file", options.getKeyFile());
      sslCnt++;
    }
    if (options.getCertFile() != null) {
      cfg.put("cert_file", options.getCertFile());
      sslCnt++;
    }
    if (options.getCaFile() != null) {
      cfg.put("ca_file", options.getCaFile());
      sslCnt++;
    }
    if (sslCnt == 3) {
      httpsPort = getFreePort();
      JsonObject ports = cfg.getJsonObject("ports", new JsonObject());
      ports.put("https", httpsPort);
      cfg.put("ports", ports).put("addresses", new JsonObject().put("https", "0.0.0.0"));
    } else {
      httpsPort = -1;
    }
    List<ConsulAgent> existingAgents = dc.getAgents();
    if (!existingAgents.isEmpty()) {
      cfg.put("start_join", existingAgents.stream()
        .map(agent -> "127.0.0.1:" + agent.getSerfLanPort())
        .collect(Collector.of(JsonArray::new, JsonArray::add, JsonArray::addAll)));
    }
    consulVersion = options.getConsulVersion();
    host = options.getAddress();
    name = options.getNodeName();
    masterToken = dc.getMasterToken();
    process = ConsulStarterBuilder.consulStarter()
      .withLogLevel(LogLevel.ERR)
      .withConsulVersion(options.getConsulVersion())
      .withCustomConfig(cfg.encode())
      .build()
      .start();
  }

  void stop() {
    process.close();
  }

  public String createAclToken(String rules) throws IOException {
    HttpClient client = new DefaultHttpClient();
    HttpPut put = new HttpPut("http://" + getHost() + ":" + getHttpPort() + "/v1/acl/create");
    put.addHeader("X-Consul-Token", masterToken);
    Map<String, String> tokenRequest = new HashMap<>();
    tokenRequest.put("Type", "client");
    tokenRequest.put("Rules", rules);
    put.setEntity(new StringEntity(Json.encode(tokenRequest)));
    HttpResponse response = client.execute(put);
    if (response.getStatusLine().getStatusCode() != 200) {
      throw new RuntimeException("Bad response");
    }
    JsonObject tokenResponse = new JsonObject(EntityUtils.toString(response.getEntity()));
    return tokenResponse.getString("ID");
  }

  public int getSerfLanPort() {
    return process.getSerfLanPort();
  }

  public int getHttpPort() {
    return process.getHttpPort();
  }

  public int getHttpsPort() {
    return httpsPort;
  }

  public String getHost() {
    return host;
  }

  public String getName() {
    return name;
  }

  public String getConsulVersion() {
    return consulVersion;
  }

  private static int getFreePort() {
    int port = -1;
    try {
      ServerSocket socket = new ServerSocket(0);
      port = socket.getLocalPort();
      socket.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
    return port;
  }
}