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

import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.junit.Assert.assertNotNull;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import jakarta.persistence.EntityManager;

import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.actionmanager.ActionDBAccessor;
import org.apache.ambari.server.actionmanager.ActionManager;
import org.apache.ambari.server.actionmanager.HostRoleCommandFactory;
import org.apache.ambari.server.actionmanager.RequestFactory;
import org.apache.ambari.server.actionmanager.StageFactory;
import org.apache.ambari.server.api.services.AmbariMetaInfo;
import org.apache.ambari.server.audit.AuditLogger;
import org.apache.ambari.server.checks.UpgradeCheckRegistry;
import org.apache.ambari.server.checks.UpgradeCheckRegistryProvider;
import org.apache.ambari.server.configuration.AmbariServerConfiguration;
import org.apache.ambari.server.controller.AbstractRootServiceResponseFactory;
import org.apache.ambari.server.controller.AmbariManagementController;
import org.apache.ambari.server.controller.KerberosHelper;
import org.apache.ambari.server.controller.KerberosHelperImpl;
import org.apache.ambari.server.controller.spi.Predicate;
import org.apache.ambari.server.controller.spi.Request;
import org.apache.ambari.server.controller.spi.Resource;
import org.apache.ambari.server.controller.spi.ResourceProvider;
import org.apache.ambari.server.controller.utilities.PredicateBuilder;
import org.apache.ambari.server.controller.utilities.PropertyHelper;
import org.apache.ambari.server.events.AgentConfigsUpdateEvent;
import org.apache.ambari.server.hooks.HookContextFactory;
import org.apache.ambari.server.hooks.HookService;
import org.apache.ambari.server.ldap.service.AmbariLdapConfigurationProvider;
import org.apache.ambari.server.metadata.RoleCommandOrderProvider;
import org.apache.ambari.server.mpack.MpackManagerFactory;
import org.apache.ambari.server.orm.dao.AlertDefinitionDAO;
import org.apache.ambari.server.orm.dao.AlertsDAO;
import org.apache.ambari.server.orm.dao.HostRoleCommandDAO;
import org.apache.ambari.server.orm.dao.RepositoryVersionDAO;
import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
import org.apache.ambari.server.sample.checks.SampleServiceCheck;
import org.apache.ambari.server.scheduler.ExecutionScheduler;
import org.apache.ambari.server.security.encryption.CredentialStoreService;
import org.apache.ambari.server.security.encryption.Encryptor;
import org.apache.ambari.server.serveraction.kerberos.KerberosConfigDataFileWriterFactory;
import org.apache.ambari.server.serveraction.kerberos.KerberosIdentityDataFileWriterFactory;
import org.apache.ambari.server.stack.StackManagerFactory;
import org.apache.ambari.server.stack.upgrade.Direction;
import org.apache.ambari.server.stack.upgrade.UpgradePack;
import org.apache.ambari.server.stack.upgrade.UpgradePack.PrerequisiteCheckConfig;
import org.apache.ambari.server.stack.upgrade.orchestrate.UpgradeHelper;
import org.apache.ambari.server.stageplanner.RoleGraphFactory;
import org.apache.ambari.server.state.CheckHelper;
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.ConfigFactory;
import org.apache.ambari.server.state.ConfigHelper;
import org.apache.ambari.server.state.DesiredConfig;
import org.apache.ambari.server.state.Service;
import org.apache.ambari.server.state.ServiceComponentFactory;
import org.apache.ambari.server.state.ServiceComponentHost;
import org.apache.ambari.server.state.ServiceComponentHostFactory;
import org.apache.ambari.server.state.ServiceInfo;
import org.apache.ambari.server.state.StackId;
import org.apache.ambari.server.state.StackInfo;
import org.apache.ambari.server.state.configgroup.ConfigGroupFactory;
import org.apache.ambari.server.state.scheduler.RequestExecutionFactory;
import org.apache.ambari.server.state.stack.OsFamily;
import org.apache.ambari.server.topology.PersistedState;
import org.apache.ambari.server.topology.TopologyManager;
import org.apache.ambari.server.topology.tasks.ConfigureClusterTaskFactory;
import org.apache.ambari.spi.ClusterInformation;
import org.apache.ambari.spi.RepositoryType;
import org.apache.ambari.spi.upgrade.UpgradeCheckStatus;
import org.apache.ambari.spi.upgrade.UpgradeCheckType;
import org.apache.ambari.spi.upgrade.UpgradeType;
import org.apache.commons.lang3.StringUtils;
import org.easymock.EasyMock;
import org.easymock.EasyMockSupport;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Matchers;
import org.powermock.api.easymock.PowerMock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.reflections.Configuration;
import org.reflections.Reflections;
import org.reflections.util.ConfigurationBuilder;
import org.springframework.security.crypto.password.PasswordEncoder;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;

/**
 * PreUpgradeCheckResourceProvider tests.
 */
@RunWith(PowerMockRunner.class)
@PrepareForTest({ UpgradeCheckRegistry.class })
public class PreUpgradeCheckResourceProviderTest extends EasyMockSupport {

  private static final String TEST_SERVICE_CHECK_CLASS_NAME = "org.apache.ambari.server.sample.checks.SampleServiceCheck";

  private static final String CLUSTER_NAME = "Cluster100";

  @Test
  @SuppressWarnings({ "rawtypes", "unchecked" })
  public void testGetResources() throws Exception{
    Injector injector = createInjector();
    AmbariManagementController managementController = injector.getInstance(AmbariManagementController.class);

    Clusters clusters = injector.getInstance(Clusters.class);
    UpgradeHelper upgradeHelper = injector.getInstance(UpgradeHelper.class);

    RepositoryVersionDAO repoDao = injector.getInstance(RepositoryVersionDAO.class);
    RepositoryVersionEntity repo = createNiceMock(RepositoryVersionEntity.class);
    UpgradePack upgradePack = createNiceMock(UpgradePack.class);
    PrerequisiteCheckConfig config = createNiceMock(PrerequisiteCheckConfig.class);

    Cluster cluster = createNiceMock(Cluster.class);
    Service service = createNiceMock(Service.class);
    ServiceInfo serviceInfo = createNiceMock(ServiceInfo.class);
    ClusterInformation clusterInformation = createNiceMock(ClusterInformation.class);

    expect(service.getDesiredRepositoryVersion()).andReturn(repo).atLeastOnce();

    StackId currentStackId = createNiceMock(StackId.class);
    StackId targetStackId = createNiceMock(StackId.class);
    AmbariMetaInfo ambariMetaInfo = injector.getInstance(AmbariMetaInfo.class);
    ambariMetaInfo.init();
    expectLastCall().anyTimes();

    Config actualConfig = createNiceMock(Config.class);
    DesiredConfig desiredConfig = createNiceMock(DesiredConfig.class);
    Map<String, DesiredConfig> configMap = Maps.newHashMap();
    configMap.put("config-type", desiredConfig);

    expect(desiredConfig.getTag()).andReturn("config-tag-1").atLeastOnce();
    expect(cluster.getDesiredConfigs()).andReturn(configMap).atLeastOnce();
    expect(cluster.getConfig("config-type", "config-tag-1")).andReturn(actualConfig).atLeastOnce();
    expect(cluster.buildClusterInformation()).andReturn(clusterInformation).anyTimes();

    expect(clusterInformation.getClusterName()).andReturn(CLUSTER_NAME).anyTimes();

    Map<String, Service> allServiceMap = new HashMap<>();
    allServiceMap.put("Service100", service);

    Map<String, ServiceInfo> allServiceInfoMap = new HashMap<>();
    allServiceInfoMap.put("Service100", serviceInfo);

    ServiceComponentHost serviceComponentHost = createNiceMock(ServiceComponentHost.class);
    expect(serviceComponentHost.getServiceName()).andReturn("Service100").atLeastOnce();
    expect(serviceComponentHost.getServiceComponentName()).andReturn("Component100").atLeastOnce();
    expect(serviceComponentHost.getHostName()).andReturn("c6401.ambari.apache.org").atLeastOnce();
    List<ServiceComponentHost> serviceComponentHosts = Lists.newArrayList();
    serviceComponentHosts.add(serviceComponentHost);
    expect(cluster.getServiceComponentHosts()).andReturn(serviceComponentHosts).atLeastOnce();

    // set expectations
    expect(managementController.getClusters()).andReturn(clusters).anyTimes();
    expect(managementController.getAmbariMetaInfo()).andReturn(ambariMetaInfo).anyTimes();

    expect(clusters.getCluster(CLUSTER_NAME)).andReturn(cluster).anyTimes();
    expect(cluster.getClusterName()).andReturn(CLUSTER_NAME).atLeastOnce();
    expect(cluster.getServices()).andReturn(allServiceMap).anyTimes();
    expect(cluster.getService("Service100")).andReturn(service).anyTimes();
    expect(cluster.getCurrentStackVersion()).andReturn(currentStackId).anyTimes();

    expect(currentStackId.getStackName()).andReturn("Stack100").anyTimes();
    expect(currentStackId.getStackVersion()).andReturn("1.0").anyTimes();
    expect(targetStackId.getStackId()).andReturn("Stack100-1.1").anyTimes();
    expect(targetStackId.getStackName()).andReturn("Stack100").anyTimes();
    expect(targetStackId.getStackVersion()).andReturn("1.1").anyTimes();

    expect(repoDao.findByPK(1L)).andReturn(repo).anyTimes();
    expect(repo.getStackId()).andReturn(targetStackId).atLeastOnce();
    expect(repo.getId()).andReturn(1L).atLeastOnce();
    expect(repo.getType()).andReturn(RepositoryType.STANDARD).atLeastOnce();
    expect(repo.getVersion()).andReturn("1.1.0.0").atLeastOnce();
    expect(upgradeHelper.suggestUpgradePack(CLUSTER_NAME, currentStackId, targetStackId, Direction.UPGRADE, UpgradeType.NON_ROLLING, "upgrade_pack11")).andReturn(upgradePack);

    List<String> prerequisiteChecks = new LinkedList<>();
    prerequisiteChecks.add(TEST_SERVICE_CHECK_CLASS_NAME);
    expect(upgradePack.getPrerequisiteCheckConfig()).andReturn(config);
    expect(upgradePack.getPrerequisiteChecks()).andReturn(prerequisiteChecks).anyTimes();
    expect(upgradePack.getTarget()).andReturn("1.1.*.*").anyTimes();
    expect(upgradePack.getOwnerStackId()).andReturn(targetStackId).atLeastOnce();
    expect(upgradePack.getType()).andReturn(UpgradeType.ROLLING).atLeastOnce();

    expect(ambariMetaInfo.getServices("Stack100", "1.0")).andReturn(allServiceInfoMap).anyTimes();
//    String checks = ClassLoader.getSystemClassLoader().getResource("checks").getPath();
    expect(serviceInfo.getChecksFolder()).andReturn(new File("checks"));


    class TestClassLoader extends URLClassLoader {
      public TestClassLoader() {
        super(new URL[0], TestClassLoader.class.getClassLoader());
      }

      @Override
      public URL[] getURLs() {
        try {
          return new URL[] { new URL("file://foo") };
        } catch (MalformedURLException e) {
          return new URL[0];
        }
      }
    }
    TestClassLoader testClassLoader = new TestClassLoader();



    StackInfo stackInfo = createNiceMock(StackInfo.class);

    expect(ambariMetaInfo.getStack(targetStackId)).andReturn(stackInfo).anyTimes();
//    String tname = targetStackId.getStackName();
//    String tv = targetStackId.getStackVersion();
    expect(ambariMetaInfo.getStack("Stack100", "1.1") ).andReturn(stackInfo).atLeastOnce();
    expect(stackInfo.getLibraryClassLoader()).andReturn(testClassLoader).anyTimes();
    expect(stackInfo.getLibraryInstance(EasyMock.anyObject(), EasyMock.eq(TEST_SERVICE_CHECK_CLASS_NAME)))
      .andReturn(new SampleServiceCheck()).atLeastOnce();

    // mock out plugin check loading
    Reflections reflectionsMock = createNiceMock(Reflections.class);

    PowerMockito.whenNew(Reflections.class).withParameterTypes(
        Configuration.class).withArguments(Matchers.any(ConfigurationBuilder.class)).thenReturn(
            reflectionsMock);

    PowerMock.replay(Reflections.class);

    // replay
    replayAll();

    ResourceProvider provider = getPreUpgradeCheckResourceProvider(managementController, injector);
    // create the request
    Request request = PropertyHelper.getReadRequest(new HashSet<>());
    PredicateBuilder builder = new PredicateBuilder();
    Predicate predicate = builder.property(PreUpgradeCheckResourceProvider.UPGRADE_CHECK_CLUSTER_NAME_PROPERTY_ID).equals(CLUSTER_NAME).and()
        .property(PreUpgradeCheckResourceProvider.UPGRADE_CHECK_UPGRADE_PACK_PROPERTY_ID).equals("upgrade_pack11").and()
        .property(PreUpgradeCheckResourceProvider.UPGRADE_CHECK_UPGRADE_TYPE_PROPERTY_ID).equals(UpgradeType.NON_ROLLING).and()
        .property(PreUpgradeCheckResourceProvider.UPGRADE_CHECK_TARGET_REPOSITORY_VERSION_ID_ID).equals("1").toPredicate();

    Set<Resource> resources = Collections.emptySet();
    resources = provider.getResources(request, predicate);

    // make sure all of the checks ran and were returned in the response; some
    // of the checks are stripped out b/c they don't define any required upgrade
    // types. The value being asserted here is a combination of built-in checks
    // which are required for the upgrade type as well as any provided checks
    // discovered in the stack
    Assert.assertEquals(20, resources.size());

    // find the service check provided by the library classloader and verify it ran
    Resource customUpgradeCheck = null;
    for (Resource resource : resources) {
      String id = (String) resource.getPropertyValue(PreUpgradeCheckResourceProvider.UPGRADE_CHECK_ID_PROPERTY_ID);
      if (StringUtils.equals(id, "SAMPLE_SERVICE_CHECK")) {
        customUpgradeCheck = resource;
        break;
      }
    }

    assertNotNull(customUpgradeCheck);

    String description = (String) customUpgradeCheck.getPropertyValue(PreUpgradeCheckResourceProvider.UPGRADE_CHECK_CHECK_PROPERTY_ID);
    Assert.assertEquals("Sample service check description.", description);
    UpgradeCheckStatus status = (UpgradeCheckStatus) customUpgradeCheck.getPropertyValue(PreUpgradeCheckResourceProvider.UPGRADE_CHECK_STATUS_PROPERTY_ID);
    Assert.assertEquals(UpgradeCheckStatus.FAIL, status);
    String reason = (String) customUpgradeCheck.getPropertyValue(PreUpgradeCheckResourceProvider.UPGRADE_CHECK_REASON_PROPERTY_ID);
    Assert.assertEquals("Sample service check always fails.", reason);
    UpgradeCheckType checkType = (UpgradeCheckType) customUpgradeCheck.getPropertyValue(PreUpgradeCheckResourceProvider.UPGRADE_CHECK_CHECK_TYPE_PROPERTY_ID);
    Assert.assertEquals(UpgradeCheckType.HOST, checkType);
    String clusterName = (String) customUpgradeCheck.getPropertyValue(PreUpgradeCheckResourceProvider.UPGRADE_CHECK_CLUSTER_NAME_PROPERTY_ID);
    Assert.assertEquals(CLUSTER_NAME, clusterName);
    UpgradeType upgradeType = (UpgradeType) customUpgradeCheck.getPropertyValue(PreUpgradeCheckResourceProvider.UPGRADE_CHECK_UPGRADE_TYPE_PROPERTY_ID);
    Assert.assertEquals(UpgradeType.NON_ROLLING, upgradeType);

  }

  /**
   * This factory method creates PreUpgradeCheckResourceProvider using the mock managementController
   */
  public PreUpgradeCheckResourceProvider getPreUpgradeCheckResourceProvider(AmbariManagementController managementController, Injector injector) throws  AmbariException {
    //UpgradeHelper upgradeHelper = injector.getInstance(UpgradeHelper.class);
    //injector.injectMembers(upgradeHelper);
    PreUpgradeCheckResourceProvider provider = new PreUpgradeCheckResourceProvider(managementController);
    return provider;
  }

  private Injector createInjector() throws Exception {
    return Guice.createInjector(new AbstractModule() {

      @Override
      @SuppressWarnings("unchecked")
      protected void configure() {
        Clusters clusters = createNiceMock(Clusters.class);
        Provider<Clusters> clusterProvider = () -> clusters;

        UpgradeHelper upgradeHelper = createNiceMock(UpgradeHelper.class);
        Provider<UpgradeHelper> upgradeHelperProvider = () -> upgradeHelper;

        CheckHelper checkHelper = new CheckHelper();

        bind(CheckHelper.class).toInstance(checkHelper);
        bind(Clusters.class).toProvider(clusterProvider);
        bind(UpgradeCheckRegistry.class).toProvider(UpgradeCheckRegistryProvider.class);
        bind(UpgradeHelper.class).toProvider(upgradeHelperProvider);
        bind(KerberosHelper.class).to(KerberosHelperImpl.class);
        bind(KerberosIdentityDataFileWriterFactory.class).toInstance(createNiceMock(KerberosIdentityDataFileWriterFactory.class));
        bind(KerberosConfigDataFileWriterFactory.class).toInstance(createNiceMock(KerberosConfigDataFileWriterFactory.class));
        bind(AuditLogger.class).toInstance(createNiceMock(AuditLogger.class));
        bind(ConfigHelper.class).toInstance(createNiceMock(ConfigHelper.class));
        bind(HostRoleCommandDAO.class).toInstance(createNiceMock(HostRoleCommandDAO.class));
        bind(ActionManager.class).toInstance(createNiceMock(ActionManager.class));
        bind(OsFamily.class).toInstance(createNiceMock(OsFamily.class));
        bind(ExecutionScheduler.class).toInstance(createNiceMock(ExecutionScheduler.class));
        bind(AmbariManagementController.class).toInstance(createNiceMock(AmbariManagementController.class));
        bind(ActionDBAccessor.class).toInstance(createNiceMock(ActionDBAccessor.class));
        bind(StackManagerFactory.class).toInstance(createNiceMock(StackManagerFactory.class));
        bind(ConfigFactory.class).toInstance(createNiceMock(ConfigFactory.class));
        bind(ConfigGroupFactory.class).toInstance(createNiceMock(ConfigGroupFactory.class));
        bind(CredentialStoreService.class).toInstance(createNiceMock(CredentialStoreService.class));
        bind(RequestExecutionFactory.class).toInstance(createNiceMock(RequestExecutionFactory.class));
        bind(RequestFactory.class).toInstance(createNiceMock(RequestFactory.class));
        bind(RoleCommandOrderProvider.class).toInstance(createNiceMock(RoleCommandOrderProvider.class));
        bind(RoleGraphFactory.class).toInstance(createNiceMock(RoleGraphFactory.class));
        bind(AbstractRootServiceResponseFactory.class).toInstance(createNiceMock(AbstractRootServiceResponseFactory.class));
        bind(ServiceComponentFactory.class).toInstance(createNiceMock(ServiceComponentFactory.class));
        bind(ServiceComponentHostFactory.class).toInstance(createNiceMock(ServiceComponentHostFactory.class));
        bind(StageFactory.class).toInstance(createNiceMock(StageFactory.class));
        bind(HostRoleCommandFactory.class).toInstance(createNiceMock(HostRoleCommandFactory.class));
        bind(HookContextFactory.class).toInstance(createNiceMock(HookContextFactory.class));
        bind(HookService.class).toInstance(createNiceMock(HookService.class));
        bind(PasswordEncoder.class).toInstance(createNiceMock(PasswordEncoder.class));
        bind(PersistedState.class).toInstance(createNiceMock(PersistedState.class));
        bind(ConfigureClusterTaskFactory.class).toInstance(createNiceMock(ConfigureClusterTaskFactory.class));
        bind(TopologyManager.class).toInstance(createNiceMock(TopologyManager.class));
        bind(AmbariMetaInfo.class).toInstance(createNiceMock(AmbariMetaInfo.class));
        bind(AlertsDAO.class).toInstance(createNiceMock(AlertsDAO.class));
        bind(AlertDefinitionDAO.class).toInstance(createNiceMock(AlertDefinitionDAO.class));
        bind(RepositoryVersionDAO.class).toInstance(createNiceMock(RepositoryVersionDAO.class));
        bind(MpackManagerFactory.class).toInstance(createNiceMock(MpackManagerFactory.class));
        Provider<EntityManager> entityManagerProvider = createNiceMock(Provider.class);
        bind(EntityManager.class).toProvider(entityManagerProvider);
        bind(new TypeLiteral<Encryptor<AgentConfigsUpdateEvent>>() {}).annotatedWith(Names.named("AgentConfigEncryptor")).toInstance(Encryptor.NONE);
        bind(new TypeLiteral<Encryptor<AmbariServerConfiguration>>() {}).annotatedWith(Names.named("AmbariServerConfigurationEncryptor")).toInstance(Encryptor.NONE);
        bind(AmbariLdapConfigurationProvider.class).toInstance(createMock(AmbariLdapConfigurationProvider.class));

        requestStaticInjection(PreUpgradeCheckResourceProvider.class);
      }
    });
  }

}
