/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */
package org.apache.iotdb.db.it;

import org.apache.iotdb.it.env.EnvFactory;
import org.apache.iotdb.it.env.cluster.node.DataNodeWrapper;
import org.apache.iotdb.it.framework.IoTDBTestRunner;
import org.apache.iotdb.itbase.category.ClusterIT;
import org.apache.iotdb.itbase.category.LocalStandaloneIT;
import org.apache.iotdb.itbase.category.RemoteIT;
import org.apache.iotdb.itbase.env.BaseEnv;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

// move to integration-test
@RunWith(IoTDBTestRunner.class)
@Category({LocalStandaloneIT.class, ClusterIT.class, RemoteIT.class})
public class GrafanaApiServiceIT {

  private int port = 18080;

  @Before
  public void setUp() throws Exception {
    BaseEnv baseEnv = EnvFactory.getEnv();
    baseEnv.getConfig().getDataNodeConfig().setEnableRestService(true);
    baseEnv.initClusterEnvironment();
    DataNodeWrapper portConflictDataNodeWrapper = EnvFactory.getEnv().getDataNodeWrapper(0);
    port = portConflictDataNodeWrapper.getRestServicePort();
  }

  @After
  public void tearDown() throws Exception {
    EnvFactory.getEnv().cleanClusterEnvironment();
  }

  private String getAuthorization(String username, String password) {
    return Base64.getEncoder()
        .encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8));
  }

  private HttpPost getHttpPost(String url) {
    HttpPost httpPost = new HttpPost(url);
    httpPost.addHeader("Content-type", "application/json; charset=utf-8");
    httpPost.setHeader("Accept", "application/json");
    String authorization = getAuthorization("root", "root");
    httpPost.setHeader("Authorization", authorization);
    return httpPost;
  }

  @Test
  public void login() {
    CloseableHttpClient httpClient = HttpClientBuilder.create().build();
    HttpGet httpGet = new HttpGet("http://127.0.0.1:" + port + "/grafana/v1/login");
    CloseableHttpResponse response = null;
    try {
      String authorization = getAuthorization("root", "root");
      httpGet.setHeader("Authorization", authorization);
      for (int i = 0; i < 30; i++) {
        try {
          response = httpClient.execute(httpGet);
          break;
        } catch (Exception e) {
          if (i == 29) {
            throw e;
          }
          try {
            Thread.sleep(1000);
          } catch (InterruptedException ex) {
            throw new RuntimeException(ex);
          }
        }
      }

      HttpEntity responseEntity = response.getEntity();
      String message = EntityUtils.toString(responseEntity, "utf-8");
      JsonObject result = JsonParser.parseString(message).getAsJsonObject();
      assertEquals(200, Integer.parseInt(result.get("code").toString()));
    } catch (IOException e) {
      e.printStackTrace();
      fail(e.getMessage());
    } finally {
      try {
        if (httpClient != null) {
          httpClient.close();
        }
        if (response != null) {
          response.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
        fail(e.getMessage());
      }
    }
  }

  public void rightInsertTablet(CloseableHttpClient httpClient) {
    CloseableHttpResponse response = null;
    try {
      HttpPost httpPost = getHttpPost("http://127.0.0.1:" + port + "/rest/v1/insertTablet");
      String json =
          "{\"timestamps\":[1635232143960,1635232153960],\"measurements\":[\"s4\",\"s5\"],\"dataTypes\":[\"INT32\",\"INT32\"],\"values\":[[11,2],[15,13]],\"isAligned\":false,\"deviceId\":\"root.sg25\"}";
      httpPost.setEntity(new StringEntity(json, Charset.defaultCharset()));
      for (int i = 0; i < 30; i++) {
        try {
          response = httpClient.execute(httpPost);
          break;
        } catch (Exception e) {
          if (i == 29) {
            throw e;
          }
          try {
            Thread.sleep(1000);
          } catch (InterruptedException ex) {
            throw new RuntimeException(ex);
          }
        }
      }
      HttpEntity responseEntity = response.getEntity();
      String message = EntityUtils.toString(responseEntity, "utf-8");
      JsonObject result = JsonParser.parseString(message).getAsJsonObject();
      assertEquals(200, Integer.parseInt(result.get("code").toString()));
    } catch (IOException e) {
      e.printStackTrace();
      fail(e.getMessage());
    } finally {
      try {
        if (response != null) {
          response.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
        fail(e.getMessage());
      }
    }
  }

  public void expressionGroupByLevel(CloseableHttpClient httpClient) {
    CloseableHttpResponse response = null;
    try {
      HttpPost httpPost = getHttpPost("http://127.0.0.1:" + port + "/grafana/v1/query/expression");
      String sql =
          "{\"expression\":[\"count(s4)\"],\"prefixPath\":[\"root.sg25\"],\"startTime\":1635232143960,\"endTime\":1635232153960,\"control\":\"group by([1635232143960,1635232153960),1s),level=1\"}";
      httpPost.setEntity(new StringEntity(sql, Charset.defaultCharset()));
      response = httpClient.execute(httpPost);
      HttpEntity responseEntity = response.getEntity();
      String message = EntityUtils.toString(responseEntity, "utf-8");
      ObjectMapper mapper = new ObjectMapper();
      Map map = mapper.readValue(message, Map.class);
      List<Long> timestampsResult = (List<Long>) map.get("timestamps");
      List<Long> expressionsResult = (List<Long>) map.get("expressions");
      List<List<Object>> valuesResult = (List<List<Object>>) map.get("values");
      Assert.assertTrue(map.size() > 0);
      Assert.assertTrue(timestampsResult.size() == 10);
      Assert.assertTrue(valuesResult.size() == 1);
      Assert.assertTrue("count(root.sg25.s4)".equals(expressionsResult.get(0)));
    } catch (IOException e) {
      e.printStackTrace();
      fail(e.getMessage());
    } finally {
      try {
        if (response != null) {
          response.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
        fail(e.getMessage());
      }
    }
  }

  public void expression(CloseableHttpClient httpClient) {
    CloseableHttpResponse response = null;
    try {
      HttpPost httpPost = getHttpPost("http://127.0.0.1:" + port + "/grafana/v1/query/expression");
      String sql =
          "{\"expression\":[\"s4\",\"s5\"],\"prefixPath\":[\"root.sg25\"],\"startTime\":1635232133960,\"endTime\":1635232163960}";
      httpPost.setEntity(new StringEntity(sql, Charset.defaultCharset()));
      for (int i = 0; i < 30; i++) {
        try {
          response = httpClient.execute(httpPost);
          break;
        } catch (Exception e) {
          if (i == 29) {
            throw e;
          }
          try {
            Thread.sleep(1000);
          } catch (InterruptedException ex) {
            throw new RuntimeException(ex);
          }
        }
      }

      HttpEntity responseEntity = response.getEntity();
      String message = EntityUtils.toString(responseEntity, "utf-8");
      ObjectMapper mapper = new ObjectMapper();
      Map<String, List> map = mapper.readValue(message, Map.class);
      String[] expressionsResult = {"root.sg25.s4", "root.sg25.s5"};
      Long[] timestamps = {1635232143960L, 1635232153960L};
      Object[] values1 = {11, 2};
      Object[] values2 = {15, 13};
      Assert.assertArrayEquals(
          expressionsResult, (map.get("expressions")).toArray(new String[] {}));
      Assert.assertArrayEquals(timestamps, (map.get("timestamps")).toArray(new Long[] {}));
      Assert.assertArrayEquals(
          values1, ((List) (map.get("values")).get(0)).toArray(new Object[] {}));
      Assert.assertArrayEquals(
          values2, ((List) (map.get("values")).get(1)).toArray(new Object[] {}));
    } catch (IOException e) {
      e.printStackTrace();
      fail(e.getMessage());
    } finally {
      try {
        if (response != null) {
          response.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
        fail(e.getMessage());
      }
    }
  }

  public void expressionWithControl(CloseableHttpClient httpClient) {
    CloseableHttpResponse response = null;
    try {
      HttpPost httpPost = getHttpPost("http://127.0.0.1:" + port + "/grafana/v1/query/expression");
      String sql =
          "{\"expression\":[\"sum(s4)\",\"avg(s5)\"],\"prefixPath\":[\"root.sg25\"],\"startTime\":1635232133960,\"endTime\":1635232163960,\"control\":\"group by([1635232133960,1635232163960),20s)\"}";
      httpPost.setEntity(new StringEntity(sql, Charset.defaultCharset()));

      for (int i = 0; i < 30; i++) {
        try {
          response = httpClient.execute(httpPost);
          break;
        } catch (Exception e) {
          if (i == 29) {
            throw e;
          }
          try {
            Thread.sleep(1000);
          } catch (InterruptedException ex) {
            throw new RuntimeException(ex);
          }
        }
      }

      HttpEntity responseEntity = response.getEntity();
      String message = EntityUtils.toString(responseEntity, "utf-8");
      ObjectMapper mapper = new ObjectMapper();
      Map<String, List> map = mapper.readValue(message, Map.class);
      String[] expressionsResult = {"sum(root.sg25.s4)", "avg(root.sg25.s5)"};
      Long[] timestamps = {1635232133960L, 1635232153960L};
      Object[] values1 = {11.0, 2.0};
      Object[] values2 = {15.0, 13.0};
      Assert.assertArrayEquals(
          expressionsResult, (map.get("expressions")).toArray(new String[] {}));
      Assert.assertArrayEquals(timestamps, (map.get("timestamps")).toArray(new Long[] {}));
      Assert.assertArrayEquals(
          values1, ((List) (map.get("values")).get(0)).toArray(new Object[] {}));
      Assert.assertArrayEquals(
          values2, ((List) (map.get("values")).get(1)).toArray(new Object[] {}));
    } catch (IOException e) {
      e.printStackTrace();
      fail(e.getMessage());
    } finally {
      try {
        if (response != null) {
          response.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
        fail(e.getMessage());
      }
    }
  }

  public void expressionWithConditionControl(CloseableHttpClient httpClient) {
    CloseableHttpResponse response = null;
    try {
      HttpPost httpPost = getHttpPost("http://127.0.0.1:" + port + "/grafana/v1/query/expression");
      String sql =
          "{\"expression\":[\"sum(s4)\",\"avg(s5)\"],\"prefixPath\":[\"root.sg25\"],\"condition\":\"timestamp=1635232143960\",\"startTime\":1635232133960,\"endTime\":1635232163960,\"control\":\"group by([1635232133960,1635232163960),20s)\"}";
      httpPost.setEntity(new StringEntity(sql, Charset.defaultCharset()));
      for (int i = 0; i < 30; i++) {
        try {
          response = httpClient.execute(httpPost);
          break;
        } catch (Exception e) {
          if (i == 29) {
            throw e;
          }
          try {
            Thread.sleep(1000);
          } catch (InterruptedException ex) {
            throw new RuntimeException(ex);
          }
        }
      }
      HttpEntity responseEntity = response.getEntity();
      String message = EntityUtils.toString(responseEntity, "utf-8");
      ObjectMapper mapper = new ObjectMapper();
      Map<String, List> map = mapper.readValue(message, Map.class);
      String[] expressionsResult = {"sum(root.sg25.s4)", "avg(root.sg25.s5)"};
      Long[] timestamps = {1635232133960L, 1635232153960L};
      Object[] values1 = {11.0, null};
      Object[] values2 = {15.0, null};
      Assert.assertArrayEquals(expressionsResult, map.get("expressions").toArray(new String[] {}));
      Assert.assertArrayEquals(timestamps, (map.get("timestamps")).toArray(new Long[] {}));
      Assert.assertArrayEquals(
          values1, ((List) (map.get("values")).get(0)).toArray(new Object[] {}));
      Assert.assertArrayEquals(
          values2, ((List) (map.get("values")).get(1)).toArray(new Object[] {}));
    } catch (IOException e) {
      e.printStackTrace();
      fail(e.getMessage());
    } finally {
      try {
        if (response != null) {
          response.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
        fail(e.getMessage());
      }
    }
  }

  public void variable(CloseableHttpClient httpClient) {
    CloseableHttpResponse response = null;
    try {
      HttpPost httpPost = getHttpPost("http://127.0.0.1:" + port + "/grafana/v1/variable");
      String sql = "{\"sql\":\"show child paths root.sg25\"}";
      httpPost.setEntity(new StringEntity(sql, Charset.defaultCharset()));
      response = httpClient.execute(httpPost);
      HttpEntity responseEntity = response.getEntity();
      String message = EntityUtils.toString(responseEntity, "utf-8");
      ObjectMapper mapper = new ObjectMapper();
      List list = mapper.readValue(message, List.class);
      String[] expectedResult = {"s4", "s5"};
      Assert.assertArrayEquals(expectedResult, list.toArray(new String[] {}));
    } catch (IOException e) {
      e.printStackTrace();
      fail(e.getMessage());
    } finally {
      try {
        if (response != null) {
          response.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
        fail(e.getMessage());
      }
    }
  }

  @Test
  public void expressionWithConditionControlTest() {
    CloseableHttpClient httpClient = HttpClientBuilder.create().build();
    rightInsertTablet(httpClient);
    expressionWithConditionControl(httpClient);
    try {
      httpClient.close();
    } catch (IOException e) {
      e.printStackTrace();
      fail(e.getMessage());
    }
  }

  @Test
  public void expressionTest() {
    CloseableHttpClient httpClient = HttpClientBuilder.create().build();
    rightInsertTablet(httpClient);
    expression(httpClient);
    // expressionGroupByLevel(httpClient);
    try {
      httpClient.close();
    } catch (IOException e) {
      e.printStackTrace();
      fail(e.getMessage());
    }
  }

  @Test
  public void expressionWithControlTest() {
    CloseableHttpClient httpClient = HttpClientBuilder.create().build();
    rightInsertTablet(httpClient);
    expressionWithControl(httpClient);
    try {
      httpClient.close();
    } catch (IOException e) {
      e.printStackTrace();
      fail(e.getMessage());
    }
  }

  @Test
  public void variableTest() {
    CloseableHttpClient httpClient = HttpClientBuilder.create().build();
    rightInsertTablet(httpClient);
    variable(httpClient);
    try {
      httpClient.close();
    } catch (IOException e) {
      e.printStackTrace();
      fail(e.getMessage());
    }
  }
}
