/**
 * 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.ambari.server.state.alerts;


import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.expect;

import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import junit.framework.TestCase;

import org.apache.ambari.server.agent.ActionQueue;
import org.apache.ambari.server.agent.AlertDefinitionCommand;
import org.apache.ambari.server.agent.AlertExecutionCommand;
import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
import org.apache.ambari.server.orm.dao.AlertDefinitionDAO;
import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
import org.apache.ambari.server.state.Cluster;
import org.apache.ambari.server.state.Clusters;
import org.apache.ambari.server.state.Config;
import org.apache.ambari.server.state.ConfigHelper;
import org.apache.ambari.server.state.Host;
import org.apache.ambari.server.state.Service;
import org.apache.ambari.server.state.ServiceComponent;
import org.apache.ambari.server.state.ServiceComponentHost;
import org.apache.ambari.server.state.alert.AlertDefinition;
import org.apache.ambari.server.state.alert.AlertDefinitionHash;
import org.apache.ambari.server.state.alert.Scope;
import org.apache.ambari.server.state.alert.SourceType;
import org.apache.commons.codec.binary.Hex;
import org.easymock.EasyMock;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.util.Modules;

/**
 * Tests for {@link AlertDefinitionHash}.
 */
public class AlertDefinitionHashTest extends TestCase {

  private AlertDefinitionHash m_hash;
  private Clusters m_mockClusters;
  private Cluster m_mockCluster;
  private AlertDefinitionDAO m_mockDao;
  private Injector m_injector;

  private static final String CLUSTERNAME = "cluster1";
  private static final String HOSTNAME = "c6401.ambari.apache.org";

  private List<AlertDefinitionEntity> m_agentDefinitions;
  private AlertDefinitionEntity m_hdfsService;
  AlertDefinitionEntity m_hdfsHost;

  /**
   *
   */
  @Override
  @Before
  @SuppressWarnings("unchecked")
  protected void setUp() throws Exception {
    super.setUp();

    m_injector = Guice.createInjector(Modules.override(
        new InMemoryDefaultTestModule()).with(new MockModule()));

    m_mockClusters = m_injector.getInstance(Clusters.class);
    m_mockCluster = m_injector.getInstance(Cluster.class);
    m_mockDao = m_injector.getInstance(AlertDefinitionDAO.class);

    // add HDFS/NN
    List<ServiceComponentHost> serviceComponentHosts = new ArrayList<ServiceComponentHost>();
    ServiceComponentHost sch = EasyMock.createNiceMock(ServiceComponentHost.class);
    expect(sch.getServiceName()).andReturn("HDFS").anyTimes();
    expect(sch.getServiceComponentName()).andReturn("NAMENODE").anyTimes();
    expect(sch.getHostName()).andReturn(HOSTNAME).anyTimes();
    EasyMock.replay(sch);
    serviceComponentHosts.add(sch);

    // add HDFS/DN
    sch = EasyMock.createNiceMock(ServiceComponentHost.class);
    expect(sch.getServiceName()).andReturn("HDFS").anyTimes();
    expect(sch.getServiceComponentName()).andReturn("DATANODE").anyTimes();
    expect(sch.getHostName()).andReturn(HOSTNAME).anyTimes();
    EasyMock.replay(sch);
    serviceComponentHosts.add(sch);

    Map<String, ServiceComponentHost> mapComponentHosts = new HashMap<String, ServiceComponentHost>();
    ServiceComponentHost host = EasyMock.createNiceMock(ServiceComponentHost.class);
    expect(host.getHostName()).andReturn(HOSTNAME).anyTimes();
    mapComponentHosts.put(HOSTNAME, host);

    Map<String, ServiceComponent> serviceComponents = new HashMap<String, ServiceComponent>();
    ServiceComponent namenode = EasyMock.createNiceMock(ServiceComponent.class);
    expect(namenode.getServiceComponentHosts()).andReturn(mapComponentHosts).anyTimes();
    expect(namenode.isMasterComponent()).andReturn(true).anyTimes();
    serviceComponents.put("NAMENODE", namenode);

    // create HDFS for the cluster
    Map<String, Service> services = new HashMap<String, Service>();
    String hdfsName = "HDFS";
    Service hdfs = EasyMock.createNiceMock(Service.class);
    expect(hdfs.getName()).andReturn("HDFS").anyTimes();
    expect(hdfs.getServiceComponents()).andReturn(serviceComponents).anyTimes();
    services.put(hdfsName, hdfs);

    // replay
    EasyMock.replay(hdfs, host, namenode);

    // Clusters mock
    expect(m_mockClusters.getCluster((String) anyObject())).andReturn(
        m_mockCluster).atLeastOnce();

    expect(m_mockClusters.getClusterById(EasyMock.anyInt())).andReturn(
        m_mockCluster).atLeastOnce();

    Map<String, Host> clusterHosts = new HashMap<String, Host>();
    clusterHosts.put(HOSTNAME, null);

    expect(m_mockClusters.getHostsForCluster(EasyMock.eq(CLUSTERNAME))).andReturn(
        clusterHosts).anyTimes();

    // cluster mock
    expect(m_mockCluster.getClusterId()).andReturn(Long.valueOf(1)).anyTimes();
    expect(m_mockCluster.getClusterName()).andReturn(CLUSTERNAME).anyTimes();
    expect(m_mockCluster.getServices()).andReturn(services).anyTimes();
    expect(
        m_mockCluster.getServiceComponentHosts(EasyMock.anyObject(String.class))).andReturn(
        serviceComponentHosts).anyTimes();

    m_hdfsService = new AlertDefinitionEntity();
    m_hdfsService.setDefinitionId(1L);
    m_hdfsService.setClusterId(1L);
    m_hdfsService.setHash(UUID.randomUUID().toString());
    m_hdfsService.setServiceName("HDFS");
    m_hdfsService.setComponentName("NAMENODE");
    m_hdfsService.setScope(Scope.SERVICE);
    m_hdfsService.setScheduleInterval(1);

    m_hdfsHost = new AlertDefinitionEntity();
    m_hdfsHost.setDefinitionId(2L);
    m_hdfsHost.setClusterId(1L);
    m_hdfsHost.setHash(UUID.randomUUID().toString());
    m_hdfsHost.setServiceName("HDFS");
    m_hdfsHost.setComponentName("DATANODE");
    m_hdfsHost.setScope(Scope.HOST);
    m_hdfsHost.setScheduleInterval(1);

    AlertDefinitionEntity agentScoped = new AlertDefinitionEntity();
    agentScoped.setDefinitionId(3L);
    agentScoped.setClusterId(1L);
    agentScoped.setHash(UUID.randomUUID().toString());
    agentScoped.setServiceName("AMBARI");
    agentScoped.setComponentName("AMBARI_AGENT");
    agentScoped.setScope(Scope.HOST);
    agentScoped.setScheduleInterval(1);

    EasyMock.expect(
        m_mockDao.findByServiceMaster(EasyMock.anyInt(),
            (Set<String>) EasyMock.anyObject())).andReturn(
        Collections.singletonList(m_hdfsService)).anyTimes();

    EasyMock.expect(
        m_mockDao.findByServiceComponent(EasyMock.anyInt(),
            EasyMock.anyObject(String.class), EasyMock.anyObject(String.class))).andReturn(
        Collections.singletonList(m_hdfsHost)).anyTimes();

    m_agentDefinitions = new ArrayList<AlertDefinitionEntity>();
    m_agentDefinitions.add(agentScoped);
    EasyMock.expect(m_mockDao.findAgentScoped(EasyMock.anyInt())).andReturn(
        m_agentDefinitions).anyTimes();

    EasyMock.replay(m_mockClusters, m_mockCluster, m_mockDao);
    m_hash = m_injector.getInstance(AlertDefinitionHash.class);
  }

  /**
   *
   */
  @Override
  @After
  protected void tearDown() throws Exception {
    super.tearDown();
  }

  /**
   * Test method for {@link org.apache.ambari.server.state.alert.AlertDefinitionHash#getHash(java.lang.String, java.lang.String)}.
   */
  @Test
  public void testGetHash() {
    String hash = m_hash.getHash(CLUSTERNAME, HOSTNAME);
    assertNotNull(hash);
    assertNotSame(AlertDefinitionHash.NULL_MD5_HASH, hash);
    assertEquals(hash, m_hash.getHash(CLUSTERNAME, HOSTNAME));
  }

  /**
   * Test method for {@link org.apache.ambari.server.state.alert.AlertDefinitionHash#getAlertDefinitions(java.lang.String, java.lang.String)}.
   */
  @Test
  public void testGetAlertDefinitions() {
    List<AlertDefinition> definitions = m_hash.getAlertDefinitions(
        CLUSTERNAME, HOSTNAME);

    assertEquals(3, definitions.size());
  }

  /**
   * Test {@link AlertDefinitionHash#invalidateAll()}.
   */
  @Test
  public void testInvalidateAll() {
    String hash = m_hash.getHash(CLUSTERNAME, HOSTNAME);
    assertNotNull(hash);

    m_hash.invalidateAll();

    String newHash = m_hash.getHash(CLUSTERNAME, HOSTNAME);
    assertEquals(hash, newHash);

    m_hash.invalidateAll();

    // add a new alert definition, forcing new hash
    AlertDefinitionEntity agentScoped = new AlertDefinitionEntity();
    agentScoped.setDefinitionId(System.currentTimeMillis());
    agentScoped.setClusterId(1L);
    agentScoped.setHash(UUID.randomUUID().toString());
    agentScoped.setServiceName("AMBARI");
    agentScoped.setComponentName("AMBARI_AGENT");
    agentScoped.setScope(Scope.HOST);
    agentScoped.setScheduleInterval(1);

    m_agentDefinitions.add(agentScoped);

    newHash = m_hash.getHash(CLUSTERNAME, HOSTNAME);
    assertNotSame(hash, newHash);
  }

  /**
   * Test {@link AlertDefinitionHash#isHashCached(String)}.
   */
  @Test
  public void testIsHashCached() {
    assertFalse(m_hash.isHashCached(CLUSTERNAME, HOSTNAME));
    String hash = m_hash.getHash(CLUSTERNAME, HOSTNAME);
    assertNotNull(hash);
    assertTrue(m_hash.isHashCached(CLUSTERNAME, HOSTNAME));

    m_hash.invalidate(HOSTNAME);
    assertFalse(m_hash.isHashCached(CLUSTERNAME, HOSTNAME));
    hash = m_hash.getHash(CLUSTERNAME, HOSTNAME);
    assertNotNull(hash);
    assertTrue(m_hash.isHashCached(CLUSTERNAME, HOSTNAME));

    m_hash.invalidateAll();
    assertFalse(m_hash.isHashCached(CLUSTERNAME, HOSTNAME));
    hash = m_hash.getHash(CLUSTERNAME, HOSTNAME);
    assertNotNull(hash);
    assertTrue(m_hash.isHashCached(CLUSTERNAME, HOSTNAME));
  }

  /**
   * Test {@link AlertDefinitionHash#invalidateHosts(AlertDefinitionEntity)}.
   */
  @Test
  public void testInvalidateHosts() {
    assertFalse(m_hash.isHashCached(CLUSTERNAME, HOSTNAME));
    String hash = m_hash.getHash(CLUSTERNAME, HOSTNAME);
    assertNotNull(hash);
    assertTrue(m_hash.isHashCached(CLUSTERNAME, HOSTNAME));

    Set<String> invalidatedHosts = m_hash.invalidateHosts(m_hdfsHost);
    assertFalse(m_hash.isHashCached(CLUSTERNAME, HOSTNAME));
    assertNotNull(invalidatedHosts);
    assertEquals(1, invalidatedHosts.size());
    assertTrue(invalidatedHosts.contains(HOSTNAME));
  }

  /**
   *
   */
  @Test
  public void testInvalidateHost() {
    assertFalse(m_hash.isHashCached(CLUSTERNAME, HOSTNAME));
    assertFalse(m_hash.isHashCached("foo", HOSTNAME));

    String hash = m_hash.getHash(CLUSTERNAME, HOSTNAME);
    assertNotNull(hash);
    assertTrue(m_hash.isHashCached(CLUSTERNAME, HOSTNAME));
    assertFalse(m_hash.isHashCached("foo", HOSTNAME));

    // invalidate the fake cluster and ensure the original cluster still
    // contains a cached valie
    m_hash.invalidate("foo", HOSTNAME);
    assertTrue(m_hash.isHashCached(CLUSTERNAME, HOSTNAME));
    assertFalse(m_hash.isHashCached("foo", HOSTNAME));

    m_hash.invalidateAll();
    assertFalse(m_hash.isHashCached(CLUSTERNAME, HOSTNAME));
    assertFalse(m_hash.isHashCached("foo", HOSTNAME));
  }

  @Test
  public void testAggregateIgnored() {
    Set<String> associatedHosts = m_hash.getAssociatedHosts(m_mockCluster,
        SourceType.AGGREGATE, "definitionName", "HDFS", null);

    assertEquals(0, associatedHosts.size());

    associatedHosts = m_hash.getAssociatedHosts(m_mockCluster, SourceType.PORT,
        "definitionName", "HDFS", null);

    assertEquals(1, associatedHosts.size());
  }

  @Test
  public void testHashingAlgorithm() throws Exception {
    List<String> uuids = new ArrayList<String>();
    uuids.add(m_hdfsService.getHash());
    uuids.add(m_hdfsHost.getHash());

    for (AlertDefinitionEntity entity : m_agentDefinitions) {
      uuids.add(entity.getHash());
    }

    Collections.sort(uuids);

    MessageDigest digest = MessageDigest.getInstance("MD5");
    for (String uuid : uuids) {
      digest.update(uuid.getBytes());
    }

    byte[] hashBytes = digest.digest();
    String expected = Hex.encodeHexString(hashBytes);

    assertEquals(expected, m_hash.getHash(CLUSTERNAME, HOSTNAME));
  }

  @Test
  public void testActionQueueInvalidation() throws Exception{
    ActionQueue actionQueue = m_injector.getInstance(ActionQueue.class);

    AlertDefinitionCommand definitionCommand1 = new AlertDefinitionCommand(
        CLUSTERNAME, HOSTNAME, "12345", null);

    AlertDefinitionCommand definitionCommand2 = new AlertDefinitionCommand(
        CLUSTERNAME, "anotherHost", "67890", null);

    AlertExecutionCommand executionCommand = new AlertExecutionCommand(
        CLUSTERNAME, HOSTNAME, null);

    actionQueue.enqueue(HOSTNAME, definitionCommand1);
    actionQueue.enqueue(HOSTNAME, executionCommand);
    actionQueue.enqueue("anotherHost", definitionCommand2);

    assertEquals(2, actionQueue.size(HOSTNAME));
    assertEquals(1, actionQueue.size("anotherHost"));

    Set<String> hosts = new HashSet<String>();
    hosts.add(HOSTNAME);

    // should invalidate both alert commands, and add a new definition command
    m_hash.enqueueAgentCommands(1L, hosts);
    assertEquals(1, actionQueue.size(HOSTNAME));
    assertEquals(1, actionQueue.size("anotherHost"));
  }

  /**
   *
   */
  private class MockModule implements Module {
    /**
     *
     */
    @Override
    public void configure(Binder binder) {
      Cluster cluster = EasyMock.createNiceMock(Cluster.class);
      EasyMock.expect(cluster.getAllConfigs()).andReturn(
          new ArrayList<Config>()).anyTimes();

      binder.bind(Clusters.class).toInstance(
          EasyMock.createNiceMock(Clusters.class));

      binder.bind(Cluster.class).toInstance(cluster);

      binder.bind(AlertDefinitionDAO.class).toInstance(
          EasyMock.createNiceMock(AlertDefinitionDAO.class));

      binder.bind(ConfigHelper.class).toInstance(
          EasyMock.createNiceMock(ConfigHelper.class));
    }
  }
}
