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

import static org.mockito.Matchers.any;

import java.util.Arrays;
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 junit.framework.Assert;

import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.actionmanager.ActionManager;
import org.apache.ambari.server.actionmanager.ExecutionCommandWrapper;
import org.apache.ambari.server.actionmanager.Request;
import org.apache.ambari.server.actionmanager.Stage;
import org.apache.ambari.server.agent.ExecutionCommand;
import org.apache.ambari.server.api.services.AmbariMetaInfo;
import org.apache.ambari.server.controller.internal.ComponentResourceProviderTest;
import org.apache.ambari.server.controller.internal.RequestOperationLevel;
import org.apache.ambari.server.controller.internal.RequestResourceFilter;
import org.apache.ambari.server.controller.internal.ServiceResourceProviderTest;
import org.apache.ambari.server.controller.spi.Resource;
import org.apache.ambari.server.orm.GuiceJpaInitializer;
import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
import org.apache.ambari.server.state.Clusters;
import org.apache.ambari.server.state.Host;
import org.apache.ambari.server.state.HostState;
import org.apache.ambari.server.state.SecurityState;
import org.apache.ambari.server.state.SecurityType;
import org.apache.ambari.server.state.State;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;

import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.persist.PersistService;

@RunWith(MockitoJUnitRunner.class)
public class
    AmbariCustomCommandExecutionHelperTest {
  private Injector injector;
  private AmbariManagementController controller;
  private AmbariMetaInfo ambariMetaInfo;
  private Clusters clusters;
  
  
  private static final String REQUEST_CONTEXT_PROPERTY = "context";
  
  @Captor ArgumentCaptor<Request> requestCapture;
  @Mock ActionManager am;
  
  @Before
  public void setup() throws Exception {
    InMemoryDefaultTestModule module = new InMemoryDefaultTestModule(){
      @Override
      protected void configure() {
        super.configure();
        bind(ActionManager.class).toInstance(am);
      }
    };
    injector = Guice.createInjector(module);
    
    
    injector.getInstance(GuiceJpaInitializer.class);
    controller = injector.getInstance(AmbariManagementController.class);
    clusters = injector.getInstance(Clusters.class);
    ambariMetaInfo = injector.getInstance(AmbariMetaInfo.class);
    ambariMetaInfo.init();
  }
  @After
  public void teardown() {
    injector.getInstance(PersistService.class).stop();
  }


  


  @SuppressWarnings("serial")
  @Test
  public void testRefreshQueueCustomCommand() {
    try {
      createClusterFixture();
      
      Map<String, String> requestProperties = new HashMap<String, String>() {
        {
          put(REQUEST_CONTEXT_PROPERTY, "Refresh YARN Capacity Scheduler");
          put("command", "REFRESHQUEUES");
        }
      };

      ExecuteActionRequest actionRequest = new ExecuteActionRequest("c1",
          "REFRESHQUEUES", new HashMap<String, String>() {
            {
              put("forceRefreshConfigTags" , "capacity-scheduler");
            }
          }, false);
      actionRequest.getResourceFilters().add(new RequestResourceFilter("YARN", "RESOURCEMANAGER",Collections.singletonList("c6401")));
      
      controller.createAction(actionRequest, requestProperties);
      
      Mockito.verify(am, Mockito.times(1)).sendActions(requestCapture.capture(), any(ExecuteActionRequest.class));

      Request request = requestCapture.getValue();
      Assert.assertNotNull(request);
      Assert.assertNotNull(request.getStages());
      Assert.assertEquals(1, request.getStages().size());
      Stage stage = request.getStages().iterator().next();
      
      Assert.assertEquals(1, stage.getHosts().size());
      
      List<ExecutionCommandWrapper> commands = stage.getExecutionCommands("c6401");
      Assert.assertEquals(1, commands.size());
      
      ExecutionCommand command = commands.get(0).getExecutionCommand();
      
      Assert.assertNotNull(command.getForceRefreshConfigTags());
      Assert.assertEquals(1, command.getForceRefreshConfigTags().size());
      Assert.assertEquals("capacity-scheduler", command.getForceRefreshConfigTags().iterator().next());
      
    } catch (AmbariException e) {
      Assert.fail(e.getMessage());
    }
  }

  @Test
  public void testHostsFilterHealthy(){
    try {
      createClusterFixture();

      Map<String, String> requestProperties = new HashMap<String, String>() {
        {
          put("context" , "Restart all components for GANGLIA");
          put("operation_level/level", "SERVICE");
          put("operation_level/service_name", "GANGLIA");
          put("operation_level/cluster_name", "c1");
        }
      };

      ExecuteActionRequest actionRequest = new ExecuteActionRequest(
         "c1", "RESTART", null,
         Arrays.asList(
            new RequestResourceFilter("GANGLIA", "GANGLIA_SERVER", Collections.singletonList("c6401")),
            new RequestResourceFilter("GANGLIA", "GANGLIA_MONITOR", Collections.singletonList("c6401")),
            new RequestResourceFilter("GANGLIA", "GANGLIA_MONITOR", Collections.singletonList("c6402"))
         ),
         new RequestOperationLevel(Resource.Type.Service, "c1", "GANGLIA", null, null),
         new HashMap<String, String>(){{}},
         false);

      controller.createAction(actionRequest, requestProperties);

      //clusters.getHost("c6402").setState(HostState.HEARTBEAT_LOST);

      Mockito.verify(am, Mockito.times(1)).sendActions(requestCapture.capture(), any(ExecuteActionRequest.class));

      Request request = requestCapture.getValue();
      Assert.assertNotNull(request);
      Assert.assertNotNull(request.getStages());
      Assert.assertEquals(1, request.getStages().size());
      Stage stage = request.getStages().iterator().next();

       // Check if was generated command, one for each host
      Assert.assertEquals(2, stage.getHostRoleCommands().size());
    }catch (Exception e) {
      Assert.fail(e.getMessage());
    }
  }

  @Test
  public void testHostsFilterUnhealthyHost(){
    try {
      createClusterFixture();

      // Set custom status to host
      clusters.getHost("c6402").setState(HostState.HEARTBEAT_LOST);
      Map<String, String> requestProperties = new HashMap<String, String>() {
        {
          put("context" , "Restart all components for GANGLIA");
          put("operation_level/level", "SERVICE");
          put("operation_level/service_name", "GANGLIA");
          put("operation_level/cluster_name", "c1");
        }
      };

      ExecuteActionRequest actionRequest = new ExecuteActionRequest(
         "c1", "RESTART", null,
         Arrays.asList(
            new RequestResourceFilter("GANGLIA", "GANGLIA_SERVER", Collections.singletonList("c6401")),
            new RequestResourceFilter("GANGLIA", "GANGLIA_MONITOR", Collections.singletonList("c6401")),
            new RequestResourceFilter("GANGLIA", "GANGLIA_MONITOR", Collections.singletonList("c6402"))
         ),
         new RequestOperationLevel(Resource.Type.Service, "c1", "GANGLIA", null, null),
         new HashMap<String, String>(){{}},
         false);

      controller.createAction(actionRequest, requestProperties);

      Mockito.verify(am, Mockito.times(1)).sendActions(requestCapture.capture(), any(ExecuteActionRequest.class));

      Request request = requestCapture.getValue();
      Assert.assertNotNull(request);
      Assert.assertNotNull(request.getStages());
      Assert.assertEquals(1, request.getStages().size());
      Stage stage = request.getStages().iterator().next();

      // Check if was generated command for one health host
      Assert.assertEquals(1, stage.getHostRoleCommands().size());
    }catch (Exception e) {
      Assert.fail(e.getMessage());
    }
  }

  @Test
  public void testHostsFilterUnhealthyComponent(){
    try {
      createClusterFixture();

      // Set custom status to host
      clusters.getCluster("c1").getService("GANGLIA").getServiceComponent("GANGLIA_MONITOR").getServiceComponentHost("c6402")
         .setState(State.UNKNOWN);
      Map<String, String> requestProperties = new HashMap<String, String>() {
        {
          put("context" , "Restart all components for GANGLIA");
          put("operation_level/level", "SERVICE");
          put("operation_level/service_name", "GANGLIA");
          put("operation_level/cluster_name", "c1");
        }
      };

      ExecuteActionRequest actionRequest = new ExecuteActionRequest(
         "c1", "RESTART", null,
         Arrays.asList(
            new RequestResourceFilter("GANGLIA", "GANGLIA_SERVER", Collections.singletonList("c6401")),
            new RequestResourceFilter("GANGLIA", "GANGLIA_MONITOR", Collections.singletonList("c6401")),
            new RequestResourceFilter("GANGLIA", "GANGLIA_MONITOR", Collections.singletonList("c6402"))
         ),
         new RequestOperationLevel(Resource.Type.Host, "c1", "GANGLIA", null, null),
         new HashMap<String, String>(){{}},
         false);

      controller.createAction(actionRequest, requestProperties);

      Mockito.verify(am, Mockito.times(1)).sendActions(requestCapture.capture(), any(ExecuteActionRequest.class));

      Request request = requestCapture.getValue();
      Assert.assertNotNull(request);
      Assert.assertNotNull(request.getStages());
      Assert.assertEquals(1, request.getStages().size());
      Stage stage = request.getStages().iterator().next();

      // Check if was generated command for one health host
      Assert.assertEquals(1, stage.getHostRoleCommands().size());
    }catch (Exception e) {
      Assert.fail(e.getMessage());
    }
  }

  private void createClusterFixture() throws AmbariException {
    createCluster("c1");
    addHost("c6401","c1");
    addHost("c6402","c1");
    
    clusters.getCluster("c1");
    createService("c1", "YARN", null);
    createService("c1", "GANGLIA", null);

    createServiceComponent("c1", "YARN","RESOURCEMANAGER", State.INIT);
    createServiceComponent("c1", "YARN", "NODEMANAGER", State.INIT);
    createServiceComponent("c1", "GANGLIA", "GANGLIA_SERVER", State.INIT);
    createServiceComponent("c1", "GANGLIA", "GANGLIA_MONITOR", State.INIT);
    
    createServiceComponentHost("c1","YARN","RESOURCEMANAGER","c6401", null);
    createServiceComponentHost("c1","YARN","NODEMANAGER","c6401", null);
    createServiceComponentHost("c1","GANGLIA","GANGLIA_SERVER","c6401", State.INIT);
    createServiceComponentHost("c1","GANGLIA","GANGLIA_MONITOR","c6401", State.INIT);
    
    createServiceComponentHost("c1","YARN","NODEMANAGER","c6402", null);
    createServiceComponentHost("c1","GANGLIA","GANGLIA_MONITOR","c6402", State.INIT);

  }
  private void addHost(String hostname, String clusterName) throws AmbariException {
    clusters.addHost(hostname);
    setOsFamily(clusters.getHost(hostname), "redhat", "6.3");
    clusters.getHost(hostname).setState(HostState.HEALTHY);
    clusters.getHost(hostname).persist();
    if (null != clusterName)
      clusters.mapHostToCluster(hostname, clusterName);
  }
  private void setOsFamily(Host host, String osFamily, String osVersion) {
    Map<String, String> hostAttributes = new HashMap<String, String>();
    hostAttributes.put("os_family", osFamily);
    hostAttributes.put("os_release_version", osVersion);
    
    host.setHostAttributes(hostAttributes);
  }

  private void createCluster(String clusterName) throws AmbariException {
    ClusterRequest r = new ClusterRequest(null, clusterName, State.INSTALLED.name(),
        SecurityType.NONE, "HDP-2.0.6", null);
    controller.createCluster(r);
  }
  
  private void createService(String clusterName,
      String serviceName, State desiredState) throws AmbariException {
    String dStateStr = null;
    if (desiredState != null) {
      dStateStr = desiredState.toString();
    }
    ServiceRequest r1 = new ServiceRequest(clusterName, serviceName, dStateStr);
    Set<ServiceRequest> requests = new HashSet<ServiceRequest>();
    requests.add(r1);

    ServiceResourceProviderTest.createServices(controller, requests);
  }

  private void createServiceComponent(String clusterName,
      String serviceName, String componentName, State desiredState)
          throws AmbariException {
    String dStateStr = null;
    if (desiredState != null) {
      dStateStr = desiredState.toString();
    }
    ServiceComponentRequest r = new ServiceComponentRequest(clusterName,
        serviceName, componentName, dStateStr);
    Set<ServiceComponentRequest> requests =
        new HashSet<ServiceComponentRequest>();
    requests.add(r);
    ComponentResourceProviderTest.createComponents(controller, requests);
  }

  private void createServiceComponentHost(String clusterName, String serviceName, String componentName, String hostname, State desiredState) throws AmbariException {
    String dStateStr = null;
    if (desiredState != null) {
      dStateStr = desiredState.toString();
    }
    ServiceComponentHostRequest r = new ServiceComponentHostRequest(clusterName,
        serviceName, componentName, hostname, dStateStr);
    Set<ServiceComponentHostRequest> requests =
        new HashSet<ServiceComponentHostRequest>();
    requests.add(r);
    controller.createHostComponents(requests);
  }

}
