package org.apache.helix.common.caches;

/*
 * 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.
 */

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.helix.MockAccessor;
import org.apache.helix.PropertyKey;
import org.apache.helix.zookeeper.datamodel.ZNRecord;
import org.apache.helix.model.CurrentState;
import org.apache.helix.model.LiveInstance;
import org.testng.Assert;
import org.testng.annotations.Test;


/**
 * Unit test for {@link CurrentStateSnapshot}
 */
public class TestCurrentStateSnapshot {

  // This test makes sure that currentStateEndTimes calculation would record correct partition replica.
  // Specifically, if a replicate has not endTime field set, we should not put an entry into currentStateEndTime
  // calculation. Otherwise, we see huge statePropagation latency of 1.4Tms.
  @Test(description = "test getNewCurrentStateEndTimes")
  public void testGetNewCurrentStateEndTimes() {
    String instance1 = "instance1";
    String session1 = "session1";
    String resource1 = "resource1";

    String partition1 = "partition1";
    String partition2 = "partition2";

    PropertyKey key = new PropertyKey.Builder("cluster").currentState(instance1, session1, resource1);

    CurrentState nxtState = new CurrentState(resource1);
    // partition 1, expect to record in endTimesMap
    nxtState.setState(partition1, "SLAVE");
    nxtState.setEndTime(partition1, 200);
    // partition 2, expect to not record in endTimeMap. This is fixing current 1.4T observed timestamp issue
    nxtState.setState(partition2, "MASTER");

    Map<PropertyKey, CurrentState> currentStateMap = new HashMap<>();
    Map<PropertyKey, CurrentState> nextStateMap = new HashMap<>();
    nextStateMap.put(key, nxtState);

    Set<PropertyKey> updateKeys = new HashSet<>();
    updateKeys.add(key);

    CurrentStateSnapshot snapshot = new CurrentStateSnapshot(nextStateMap, currentStateMap, updateKeys);

    Map<PropertyKey, Map<String, Long>> endTimesMap = snapshot.getNewCurrentStateEndTimes();

    Assert.assertEquals(endTimesMap.size(), 1);
    Assert.assertTrue(endTimesMap.get(key).get(partition1) == 200);
  }

  // This test makes sure that all the changed current State is reflected in newCurrentStateEndTimes calculation.
  // Previously, we have bugs that all newly created current state would be reflected in newCurrentStateEndTimes
  // calculation.
  @Test(description = "testRefreshCurrentStateCache")
  public void testRefreshCurrentStateCache() {
    String instanceName = "instance1";
    long instanceSession = 12345;
    String resourceName = "resource";
    String partitionName = "resource_partition1";

    MockAccessor accessor = new MockAccessor();
    PropertyKey.Builder keyBuilder = accessor.keyBuilder();

    // construct liveInstance
    ZNRecord record = new ZNRecord(instanceName);
    record.setEphemeralOwner(instanceSession);
    LiveInstance instance = new LiveInstance(record);

    boolean retVal = accessor.setProperty(keyBuilder.liveInstance(instanceName), instance);
    Assert.assertTrue(retVal);

    // construct currentstate
    CurrentState originState = new CurrentState(resourceName);
    originState.setEndTime(partitionName, 100);

    CurrentState currentState = new CurrentState(resourceName);
    currentState.setEndTime(partitionName, 300);
    retVal = accessor.setProperty(keyBuilder.currentState(instanceName, instance.getEphemeralOwner(), resourceName),
        originState);
    Assert.assertTrue(retVal);

    CurrentStateCache cache = new CurrentStateCache("cluster");

    Map<String, LiveInstance> liveInstanceMap = new HashMap<>();
    liveInstanceMap.put(instanceName, instance);

    retVal = cache.refresh(accessor, liveInstanceMap);
    Assert.assertTrue(retVal);

    retVal = accessor.setProperty(keyBuilder.currentState(instanceName, instance.getEphemeralOwner(), resourceName),
        currentState);
    Assert.assertTrue(retVal);

    retVal = cache.refresh(accessor, liveInstanceMap);
    Assert.assertTrue(retVal);

    CurrentStateSnapshot snapshot = cache.getSnapshot();

    Map<PropertyKey, Map<String, Long>> endTimesMap = snapshot.getNewCurrentStateEndTimes();

    Assert.assertEquals(endTimesMap.size(), 1);
    // note, without this fix, the endTimesMap would be size zero.
    Assert.assertTrue(endTimesMap.get(keyBuilder.currentState(instanceName, instance.getEphemeralOwner(), resourceName))
        .get(partitionName) == 300);
  }
}
