/*
 * 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.geode.management.internal.cli.commands;

import static org.apache.geode.management.internal.cli.commands.DataCommandsUtils.getRegionAssociatedMembers;
import static org.assertj.core.api.Assertions.assertThat;

import java.io.Serializable;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.apache.commons.collections.CollectionUtils;
import org.awaitility.Awaitility;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;

import org.apache.geode.cache.Cache;
import org.apache.geode.cache.CacheFactory;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionShortcut;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.management.internal.cli.dto.Key1;
import org.apache.geode.management.internal.cli.dto.Value2;
import org.apache.geode.test.dunit.SerializableCallableIF;
import org.apache.geode.test.dunit.rules.LocatorServerStartupRule;
import org.apache.geode.test.dunit.rules.MemberVM;
import org.apache.geode.test.junit.categories.DistributedTest;
import org.apache.geode.test.junit.rules.GfshShellConnectionRule;

@Category(DistributedTest.class)
public class RemoveCommandJsonDUnitTest implements Serializable {
  private static final String JSON_REGION_NAME = "jsonReplicateRegion";

  @Rule
  public LocatorServerStartupRule locatorServerStartupRule = new LocatorServerStartupRule();

  @Rule
  public transient GfshShellConnectionRule gfsh = new GfshShellConnectionRule();

  private transient MemberVM locator;
  private transient MemberVM server1;
  private transient MemberVM server2;

  @Before
  public void setup() throws Exception {
    locator = locatorServerStartupRule.startLocatorVM(0);

    server1 = locatorServerStartupRule.startServerVM(1, locator.getPort());
    server2 = locatorServerStartupRule.startServerVM(2, locator.getPort());

    server1.invoke(this::populateTestRegions);
    server2.invoke(this::populateTestRegions);

    gfsh.connectAndVerify(locator);

    Awaitility.await().atMost(2, TimeUnit.MINUTES).until(() -> locator.getVM()
        .invoke((SerializableCallableIF<Boolean>) this::regionMBeansAreInitialized));
  }

  private void populateTestRegions() {
    Cache cache = CacheFactory.getAnyInstance();

    Region<Key1, Value2> complexRegion =
        cache.<Key1, Value2>createRegionFactory(RegionShortcut.REPLICATE).create(JSON_REGION_NAME);

    Value2 value1 = new Value2();
    value1.setStateName("State1");
    value1.setCapitalCity("capital1");
    value1.setPopulation(100);
    value1.setAreaInSqKm(100.4365);
    complexRegion.put(key(1), value1);

    Value2 value2 = new Value2();
    value1.setStateName("State2");
    value1.setCapitalCity("capital2");
    value1.setPopulation(200);
    value1.setAreaInSqKm(200.4365);
    complexRegion.put(key(2), value2);
  }

  @Test
  public void testRemoveJsonCommand() {
    String keyJson = "('id':'key1','name':'name1')";

    String command = removeCommand(keyJson);
    gfsh.executeAndVerifyCommand(command);

    server1.invoke(() -> verifyKeyIsRemoved(1));
    server2.invoke(() -> verifyKeyIsRemoved(1));

    server1.invoke(() -> verifyKeyIsPresent(2));
    server2.invoke(() -> verifyKeyIsPresent(2));
  }

  @Test
  public void testRemoveJsonCommandWithOnlyId() {
    String keyJson = "('id':'key1')";

    String command = removeCommand(keyJson);
    gfsh.executeAndVerifyCommand(command);

    server1.invoke(() -> verifyKeyIsRemoved(1));
    server2.invoke(() -> verifyKeyIsRemoved(1));

    server1.invoke(() -> verifyKeyIsPresent(2));
    server2.invoke(() -> verifyKeyIsPresent(2));
  }

  @Test
  public void testRemoveJsonCommandWithInvalidJson() {
    String keyJson = "('foo':'bar')";

    String command = removeCommand(keyJson);
    gfsh.executeAndVerifyCommand(command);

    server1.invoke(() -> verifyKeyIsPresent(1));
    server2.invoke(() -> verifyKeyIsPresent(1));

    server1.invoke(() -> verifyKeyIsPresent(2));
    server2.invoke(() -> verifyKeyIsPresent(2));
  }

  private String removeCommand(String keyJson) {
    return "remove --key=" + keyJson + " --region=" + JSON_REGION_NAME + " --key-class="
        + Key1.class.getCanonicalName();
  }

  private Key1 key(int n) {
    Key1 key = new Key1();
    key.setId("key" + n);
    key.setName("name" + n);

    return key;
  }

  private boolean regionMBeansAreInitialized() {
    Set<DistributedMember> members = getRegionAssociatedMembers(JSON_REGION_NAME,
        (InternalCache) CacheFactory.getAnyInstance(), false);

    return CollectionUtils.isNotEmpty(members);
  }

  private void verifyKeyIsRemoved(int n) {
    Region region = getJsonRegion();
    assertThat(region.get(key(n))).isNull();
  }

  private void verifyKeyIsPresent(int n) {
    Region region = getJsonRegion();
    assertThat(region.get(key(n))).isNotNull();
  }

  private Region getJsonRegion() {
    return CacheFactory.getAnyInstance().getRegion(JSON_REGION_NAME);
  }
}
