/*
 * 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.autoscaling.sim;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintStream;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.apache.solr.client.solrj.cloud.autoscaling.Suggester;
import org.apache.solr.cloud.CloudUtil;
import org.apache.solr.common.util.Utils;
import org.apache.solr.util.LogLevel;
import org.junit.Test;

/**
 *
 */
@LogLevel("org.apache.solr.cloud.autoscaling=DEBUG")
public class TestSimScenario extends SimSolrCloudTestCase {

  // simple scenario to test .autoAddReplicas trigger
  String autoAddReplicasScenario =
      "# standard comment\n" +
      "// java comment\n" +
      "create_cluster numNodes=2 // inline comment\n" +
      "load_autoscaling json={'cluster-policy'+:+[{'replica'+:+'<3',+'shard'+:+'#EACH',+'collection'+:+'testCollection','node':'#ANY'}]}&defaultWaitFor=10\n" +
      "solr_request /admin/collections?action=CREATE&autoAddReplicas=true&name=testCollection&numShards=2&replicationFactor=2&maxShardsPerNode=2\n" +
      "wait_collection collection=testCollection&shards=2&replicas=2\n" +
      "event_listener trigger=.auto_add_replicas&stage=SUCCEEDED\n" +
      "kill_nodes node=${_random_node_}\n" +
      "wait_event trigger=.auto_add_replicas&wait=60\n" +
      "wait_collection collection=testCollection&shards=2&replicas=2\n" +
      "save_snapshot path=${snapshotPath}\n";

  @Test
  public void testAutoAddReplicas() throws Exception {
    String snapshotPath = createTempDir() + "/snapshot";
    try (SimScenario scenario = SimScenario.load(autoAddReplicasScenario)) {
      scenario.context.put("snapshotPath", snapshotPath);
      scenario.run();
    }
    SnapshotCloudManager snapshotCloudManager = SnapshotCloudManager.readSnapshot(new File(snapshotPath));
    CloudUtil.waitForState(snapshotCloudManager, "testCollection", 1, TimeUnit.SECONDS,
        CloudUtil.clusterShape(2, 2));
  }

  String testSuggestionsScenario =
      "create_cluster numNodes=2\n" +
      "solr_request /admin/collections?action=CREATE&autoAddReplicas=true&name=testCollection&numShards=2&replicationFactor=2&maxShardsPerNode=2\n" +
      "wait_collection collection=testCollection&shards=2&replicas=2\n" +
      "ctx_set key=myNode&value=${_random_node_}\n" +
      "solr_request /admin/collections?action=ADDREPLICA&collection=testCollection&shard=shard1&node=${myNode}\n" +
      "solr_request /admin/collections?action=ADDREPLICA&collection=testCollection&shard=shard1&node=${myNode}\n" +
      "loop_start iterations=${iterative}\n" +
      "  calculate_suggestions\n" +
      "  apply_suggestions\n" +
      "  solr_request /admin/collections?action=ADDREPLICA&collection=testCollection&shard=shard1&node=${myNode}\n" +
      "  solr_request /admin/collections?action=ADDREPLICA&collection=testCollection&shard=shard1&node=${myNode}\n" +
      "loop_end\n" +
      "loop_start iterations=${justCalc}\n" +
      "  calculate_suggestions\n" +
      "  save_snapshot path=${snapshotPath}/${_loop_iter_}\n" +
      "loop_end\n" +
      "dump redact=true";

  @Test
  public void testSuggestions() throws Exception {
    String snapshotPath = createTempDir() + "/snapshot";
    try (SimScenario scenario = SimScenario.load(testSuggestionsScenario)) {
      scenario.context.put("snapshotPath", snapshotPath);
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      PrintStream ps = new PrintStream(baos, true, "UTF-8");
      scenario.console = ps;
      scenario.context.put("iterative", "0");
      scenario.context.put("justCalc", "1");
      scenario.run();
      List<Suggester.SuggestionInfo> suggestions = (List<Suggester.SuggestionInfo>)scenario.context.get(SimScenario.SUGGESTIONS_CTX_PROP);
      assertNotNull(suggestions);
      assertEquals(suggestions.toString(), 1, suggestions.size());
      // reconstruct the snapshot from the dump
      Map<String, Object> snapshot = (Map<String, Object>)Utils.fromJSON(baos.toByteArray());
      Map<String, Object> autoscalingState = (Map<String, Object>)snapshot.get(SnapshotCloudManager.AUTOSCALING_STATE_KEY);
      assertNotNull(autoscalingState);
      assertEquals(autoscalingState.toString(), 1, autoscalingState.size());
      assertTrue(autoscalingState.toString(), autoscalingState.containsKey("suggestions"));
      List<Map<String, Object>> snapSuggestions = (List<Map<String, Object>>)autoscalingState.get("suggestions");
      assertEquals(snapSuggestions.toString(), 1, snapSuggestions.size());
      // _loop_iter_ should be present and 0 (first iteration)
      assertEquals("0", scenario.context.get(SimScenario.LOOP_ITER_PROP));
    }
    // try looping more times
    try (SimScenario scenario = SimScenario.load(testSuggestionsScenario)) {
      scenario.context.put("iterative", "10");
      scenario.context.put("justCalc", "0");
      scenario.run();
      assertEquals("9", scenario.context.get(SimScenario.LOOP_ITER_PROP));
    }

  }

  String indexingScenario =
      "create_cluster numNodes=100\n" +
      "solr_request /admin/collections?action=CREATE&autoAddReplicas=true&name=testCollection&numShards=2&replicationFactor=2&maxShardsPerNode=2\n" +
      "wait_collection collection=testCollection&shards=2&replicas=2\n" +
      "solr_request /admin/autoscaling?httpMethod=POST&stream.body=" +
          "{'set-trigger':{'name':'indexSizeTrigger','event':'indexSize','waitFor':'10s','aboveDocs':1000,'enabled':true,"+
          "'actions':[{'name':'compute_plan','class':'solr.ComputePlanAction'},{'name':'execute_plan','class':'solr.ExecutePlanAction'}]}}\n" +
      "event_listener trigger=indexSizeTrigger&stage=SUCCEEDED\n" +
      "index_docs collection=testCollection&numDocs=3000\n" +
      "run\n" +
      "wait_event trigger=indexSizeTrigger&wait=60\n" +
      "assert condition=not_null&key=_trigger_event_indexSizeTrigger\n" +
      "assert condition=equals&key=_trigger_event_indexSizeTrigger/eventType&expected=INDEXSIZE\n" +
      "assert condition=equals&key=_trigger_event_indexSizeTrigger/properties/requestedOps[0]/action&expected=SPLITSHARD\n" +
      "wait_collection collection=testCollection&shards=6&withInactive=true&requireLeaders=false&replicas=2";

  @Test
  public void testIndexing() throws Exception {
    try (SimScenario scenario = SimScenario.load(indexingScenario)) {
      scenario.run();
    }
  }

  String splitShardScenario =
      "create_cluster numNodes=2\n" +
          "solr_request /admin/collections?action=CREATE&name=testCollection&numShards=2&replicationFactor=2&maxShardsPerNode=5\n" +
          "wait_collection collection=testCollection&shards=2&replicas=2\n" +
          "set_shard_metrics collection=testCollection&shard=shard1&INDEX.sizeInBytes=1000000000\n" +
          "set_node_metrics nodeset=#ANY&freedisk=1.5\n" +
          "solr_request /admin/collection?action=SPLITSHARD&collection=testCollection&shard=shard1&splitMethod=${method}\n" +
          "wait_collection collection=testCollection&shards=4&&withInactive=true&replicas=2&requireLeaders=true\n"
      ;
  @Test
  public void testSplitShard() throws Exception {
    try (SimScenario scenario = SimScenario.load(splitShardScenario)) {
      scenario.context.put("method", "REWRITE");
      scenario.run();
    } catch (Exception e) {
      assertTrue(e.toString(), e.toString().contains("not enough free disk"));
    }
    try (SimScenario scenario = SimScenario.load(splitShardScenario)) {
      scenario.context.put("method", "LINK");
      scenario.run();
    } catch (Exception e) {
      fail("should have succeeded with method LINK, but failed: " + e.toString());
    }
  }
}
