package su.xserver.iikocon.iiko;

import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.WebClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;

public class IikoOlapClient {

  private static final Logger log = LoggerFactory.getLogger("[IikoOlapClient]");
  private final WebClient webClient;
  private final String iikoHost;
  private final String iikoLogin;
  private final String iikoPassHash;

  public IikoOlapClient(Vertx vertx, JsonObject rest) {
    this.webClient = WebClient.create(vertx);
    this.iikoHost = (rest.getBoolean("https") ? "https://" : "http://") + rest.getString("host") + (rest.getBoolean("https") ? ":443" : ":80");
    this.iikoLogin = rest.getString("login");
    this.iikoPassHash = rest.getString("password");
  }

  private Future<String> authenticate() {
    Promise<String> promise = Promise.promise();
    String url = iikoHost + "/resto/api/auth";

    webClient.getAbs(url)
      .addQueryParam("login", iikoLogin)
      .addQueryParam("pass", iikoPassHash)
      .send()
      .onSuccess(resp -> {
        if (resp.statusCode() == 200) {
          String token = resp.bodyAsString();
          log.info("Authenticated, token: {}", token);
          promise.complete(token);
        } else {
          promise.fail("Auth failed for " + iikoLogin + ": " + resp.statusCode());
        }
      })
      .onFailure(promise::fail);
    return promise.future();
  }

  private Future<Void> logout(String token) {
    if (token == null || token.isEmpty()) {
      return Future.succeededFuture();
    }
    Promise<Void> promise = Promise.promise();
    String url = iikoHost + "/resto/api/logout";
    webClient.getAbs(url)
      .addQueryParam("key", token)
      .send()
      .onSuccess(resp -> {
        log.info(resp.bodyAsString());
        promise.complete();
      })
      .onFailure(err -> {
        log.error("Logout request failed: {}", err.getMessage());
        promise.complete();
      });
    return promise.future();
  }

  public Future<JsonObject> checkConnection() {
    Promise<JsonObject> promise = Promise.promise();

    long time = System.currentTimeMillis();

    authenticate()
      .onSuccess(token -> {
        logout(token).mapEmpty();
        promise.complete(new JsonObject()
          .put("success", true)
          .put("latency_ms", System.currentTimeMillis() - time)
        );
      })
      .onFailure(promise::fail);
    return promise.future();
  }

  public Future<JsonObject> handleGet(String uri, JsonObject params) {
    Promise<JsonObject> promise = Promise.promise();

    authenticate()
      .onSuccess(token -> {
        String url = appendQueryParams(iikoHost + uri, params.put("key", token));
        log.info("Request to : {}", url);
        webClient.getAbs(url)
          .send()
          .onSuccess(resp -> {
            if (resp.statusCode() == 200) {
              JsonObject body = resp.bodyAsJsonObject();
              JsonObject data = body.containsKey("data") && body.getValue("data") instanceof JsonObject
                ? body.getJsonObject("data")
                : body;
              logout(token).mapEmpty();
              promise.complete(data);
            } else {
              promise.fail("Failed request to " + iikoHost + ": HTTP " + resp.statusCode());
            }
          })
          .onFailure(promise::fail);
      })
      .onFailure(promise::fail);

    return promise.future();
  }

  private String toQueryString(JsonObject params) {
    if (params == null || params.isEmpty()) {
      return "";
    }
    return "?" + params.stream()
      .map(entry -> URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8) + "=" +
        URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8))
      .collect(Collectors.joining("&"));
  }

  private String appendQueryParams(String url, JsonObject params) {
    return url + toQueryString(params);
  }

}
