/*
 * 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.solr.cloud;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.imageio.ImageIO;

import org.apache.lucene.util.LuceneTestCase;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.cloud.SolrCloudManager;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.GenericSolrRequest;
import org.apache.solr.cloud.autoscaling.sim.SimCloudManager;
import org.apache.solr.common.params.CollectionAdminParams;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.Base64;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.common.util.TimeSource;
import org.apache.solr.util.LogLevel;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 */
@LuceneTestCase.Slow
@LogLevel("org.apache.solr.handler.admin=DEBUG")
public class MetricsHistoryIntegrationTest extends SolrCloudTestCase {
  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

  private static SolrCloudManager cloudManager;
  private static SolrClient solrClient;
  private static TimeSource timeSource;

  @BeforeClass
  public static void setupCluster() throws Exception {
    boolean simulated = TEST_NIGHTLY ? random().nextBoolean() : true;
    if (simulated) {
      cloudManager = SimCloudManager.createCluster(1, TimeSource.get("simTime:50"));
      solrClient = ((SimCloudManager)cloudManager).simGetSolrClient();
    }
    configureCluster(1)
        .addConfig("conf", configset("cloud-minimal"))
        .configure();
    if (!simulated) {
      cloudManager = cluster.getJettySolrRunner(0).getCoreContainer().getZkController().getSolrCloudManager();
      solrClient = cluster.getSolrClient();
    }
    timeSource = cloudManager.getTimeSource();
    // create .system
    CollectionAdminRequest.createCollection(CollectionAdminParams.SYSTEM_COLL, null, 1, 1)
        .process(solrClient);
    CloudUtil.waitForState(cloudManager, CollectionAdminParams.SYSTEM_COLL,
        30, TimeUnit.SECONDS, CloudUtil.clusterShape(1, 1));
    solrClient.query(CollectionAdminParams.SYSTEM_COLL, params(CommonParams.Q, "*:*"));
    // sleep a little to allow the handler to collect some metrics
    if (simulated) {
      timeSource.sleep(90000);
    } else {
      timeSource.sleep(100000);
    }
  }

  @AfterClass
  public static void teardown() throws Exception {
    if (cloudManager instanceof SimCloudManager) {
      cloudManager.close();
    }
    solrClient = null;
    cloudManager = null;
  }

  @Test
  public void testList() throws Exception {
    NamedList<Object> rsp = solrClient.request(createHistoryRequest(params(CommonParams.ACTION, "list")));
    assertNotNull(rsp);
    // expected solr.jvm, solr.node and solr.collection..system
    SimpleOrderedMap<Object> lst = (SimpleOrderedMap<Object>) rsp.get("metrics");
    assertNotNull(lst);
    assertEquals(lst.toString(), 3, lst.size());
    assertNotNull(lst.toString(), lst.get("solr.jvm"));
    assertNotNull(lst.toString(), lst.get("solr.node"));
    assertNotNull(lst.toString(), lst.get("solr.collection..system"));
  }

  @Test
  public void testStatus() throws Exception {
    NamedList<Object> rsp = solrClient.request(createHistoryRequest(
        params(CommonParams.ACTION, "status", CommonParams.NAME, "solr.jvm")));
    assertNotNull(rsp);
    NamedList<Object> map = (NamedList<Object>)rsp.get("metrics");
    assertEquals(map.toString(), 1, map.size());
    map = (NamedList<Object>)map.get("solr.jvm");
    assertNotNull(map);
    NamedList<Object> status = (NamedList<Object>)map.get("status");
    assertNotNull(status);
    assertEquals(status.toString(), 7, status.size());
    List<Object> lst = (List<Object>)status.get("datasources");
    assertNotNull(lst);
    assertEquals(lst.toString(), 3, lst.size());
    lst = (List<Object>)status.get("archives");
    assertNotNull(lst);
    assertEquals(lst.toString(), 5, lst.size());
  }

  @Test
  public void testGet() throws Exception {
    NamedList<Object> rsp = solrClient.request(createHistoryRequest(params(
        CommonParams.ACTION, "get", CommonParams.NAME, "solr.jvm")));
    assertNotNull(rsp);
    // default format is LIST
    NamedList<Object> data = (NamedList<Object>)rsp.findRecursive("metrics", "solr.jvm", "data");
    assertNotNull(data);
    data.forEach((k, v) -> {
      NamedList<Object> entry = (NamedList<Object>)v;
      List<Object> lst = entry.getAll("timestamps");
      assertNotNull(lst);
      assertTrue("timestamps", lst.size() > 0);
      // 3 metrics, so the total size of values is 3 * the size of timestamps
      entry = (NamedList<Object>)entry.get("values");
      assertNotNull(entry);
      assertEquals(lst.size() * 3, entry.size());
    });

    // get STRING
    rsp = solrClient.request(createHistoryRequest(params(
        CommonParams.ACTION, "get", CommonParams.NAME, "solr.jvm", "format", "string")));
    data = (NamedList<Object>)rsp.findRecursive("metrics", "solr.jvm", "data");
    assertNotNull(data);
    data.forEach((k, v) -> {
      NamedList<Object> entry = (NamedList<Object>)v;
      List<Object> lst = entry.getAll("timestamps");
      assertNotNull(lst);
      assertEquals("timestamps", 1, lst.size());
      String timestampString = (String)lst.get(0);
      String[] timestamps = timestampString.split(("\n"));
      assertTrue(timestampString, timestamps.length > 1);
      entry = (NamedList<Object>)entry.get("values");
      assertNotNull(entry);
      assertEquals(3, entry.size());
      entry.forEach((vk, vv) -> {
        String valString = (String)vv;
        String[] values = valString.split("\n");
        assertEquals(valString, timestamps.length, values.length);
      });
    });

    // get GRAPH
    rsp = solrClient.request(createHistoryRequest(params(
        CommonParams.ACTION, "get", CommonParams.NAME, "solr.jvm", "format", "graph")));
    data = (NamedList<Object>)rsp.findRecursive("metrics", "solr.jvm", "data");
    assertNotNull(data);
    data.forEach((k, v) -> {
      NamedList<Object> entry = (NamedList<Object>) v;
      entry = (NamedList<Object>)entry.get("values");
      assertNotNull(entry);
      assertEquals(3, entry.size());
      entry.forEach((vk, vv) -> {
        String valString = (String)vv;
        byte[] img = Base64.base64ToByteArray(valString);
        try {
          ImageIO.read(new ByteArrayInputStream(img));
        } catch (IOException e) {
          fail("Error reading image data: " + e.toString());
        }
      });
    });
  }

  public static SolrRequest createHistoryRequest(SolrParams params) {
    return new GenericSolrRequest(SolrRequest.METHOD.GET, "/admin/metrics/history", params);
  }

}
