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

import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.anyString;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;

import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import jakarta.persistence.EntityManager;

import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.actionmanager.HostRoleCommand;
import org.apache.ambari.server.actionmanager.HostRoleStatus;
import org.apache.ambari.server.agent.CommandReport;
import org.apache.ambari.server.agent.ExecutionCommand;
import org.apache.ambari.server.audit.AuditLogger;
import org.apache.ambari.server.controller.KerberosHelper;
import org.apache.ambari.server.security.credential.PrincipalKeyCredential;
import org.apache.ambari.server.serveraction.kerberos.stageutils.KerberosKeytabController;
import org.apache.ambari.server.serveraction.kerberos.stageutils.ResolvedKerberosKeytab;
import org.apache.ambari.server.serveraction.kerberos.stageutils.ResolvedKerberosPrincipal;
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.kerberos.KerberosIdentityDescriptor;
import org.apache.ambari.server.state.stack.OsFamily;
import org.easymock.EasyMockSupport;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.google.common.collect.Sets;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;

import junit.framework.Assert;

public class KerberosServerActionTest extends EasyMockSupport {

  private  static final Map<String, String> KERBEROS_ENV_PROPERTIES = Collections.singletonMap("admin_server_host", "kdc.example.com");

  Map<String, String> commandParams = new HashMap<>();
  File temporaryDirectory;
  private Injector injector;
  private KerberosServerAction action;
  private Cluster cluster;
  private KerberosKeytabController kerberosKeytabController;

  @Before
  public void setUp() throws Exception {

    Config kerberosEnvConfig = createMock(Config.class);
    expect(kerberosEnvConfig.getProperties()).andReturn(KERBEROS_ENV_PROPERTIES).anyTimes();

    cluster = createMock(Cluster.class);
    expect(cluster.getDesiredConfigByType("kerberos-env")).andReturn(kerberosEnvConfig).anyTimes();

    Clusters clusters = createMock(Clusters.class);
    expect(clusters.getCluster(anyString())).andReturn(cluster).anyTimes();

    ExecutionCommand mockExecutionCommand = createMock(ExecutionCommand.class);
    HostRoleCommand mockHostRoleCommand = createMock(HostRoleCommand.class);
    kerberosKeytabController = createMock(KerberosKeytabController.class);
    expect(kerberosKeytabController.adjustServiceComponentFilter(anyObject(), eq(true), anyObject())).andReturn(null).anyTimes();
    expect(kerberosKeytabController.getFilteredKeytabs((Collection<KerberosIdentityDescriptor>)null, null, null))
      .andReturn(
        Sets.newHashSet(new ResolvedKerberosKeytab(
          null,
          null,
          null,
          null,
          null,
          Sets.newHashSet(new ResolvedKerberosPrincipal(1l, "host", "principal", true, "/tmp", "SERVICE", "COMPONENT", "/tmp")),
          true,
          true))
      ).anyTimes();

    action = new KerberosServerAction() {

      @Override
      protected CommandReport processIdentity(ResolvedKerberosPrincipal resolvedPrincipal,
                                              KerberosOperationHandler operationHandler,
                                              Map<String, String> kerberosConfiguration,
                                              boolean includedInFilter,
                                              Map<String, Object> requestSharedDataContext)
          throws AmbariException {
        Assert.assertNotNull(requestSharedDataContext);

        if (requestSharedDataContext.get("FAIL") != null) {
          return createCommandReport(1, HostRoleStatus.FAILED, "{}", "ERROR", "ERROR");
        } else {
          requestSharedDataContext.put(resolvedPrincipal.getPrincipal(), resolvedPrincipal.getPrincipal());
          return null;
        }
      }

      @Override
      public CommandReport execute(ConcurrentMap<String, Object> requestSharedDataContext)
          throws AmbariException, InterruptedException {
        return processIdentities(requestSharedDataContext);
      }
    };
    action.setExecutionCommand(mockExecutionCommand);
    action.setHostRoleCommand(mockHostRoleCommand);

    injector = Guice.createInjector(new AbstractModule() {

      @Override
      protected void configure() {
        bind(KerberosHelper.class).toInstance(createMock(KerberosHelper.class));
        bind(KerberosServerAction.class).toInstance(action);
        bind(EntityManager.class).toInstance(createNiceMock(EntityManager.class));

        bind(Clusters.class).toInstance(clusters);
        bind(OsFamily.class).toInstance(createNiceMock(OsFamily.class));
        bind(AuditLogger.class).toInstance(createNiceMock(AuditLogger.class));
        bind(KerberosOperationHandlerFactory.class).toInstance(createMock(KerberosOperationHandlerFactory.class));
        bind(KerberosKeytabController.class).toInstance(kerberosKeytabController);
      }
    });

    temporaryDirectory = File.createTempFile("ambari_ut_", ".d");

    Assert.assertTrue(temporaryDirectory.delete());
    Assert.assertTrue(temporaryDirectory.mkdirs());

    // Create a data file
    KerberosIdentityDataFileWriter writer =
        new KerberosIdentityDataFileWriter(new File(temporaryDirectory, KerberosIdentityDataFileWriter.DATA_FILE_NAME));
    for (int i = 0; i < 10; i++) {
      writer.writeRecord("hostName", "serviceName" + i, "serviceComponentName" + i,
          "principal|_HOST|_REALM" + i, "principal_type", "keytabFilePath" + i,
          "keytabFileOwnerName" + i, "keytabFileOwnerAccess" + i,
          "keytabFileGroupName" + i, "keytabFileGroupAccess" + i,
          "false");
    }
    writer.close();

    commandParams.put(KerberosServerAction.DATA_DIRECTORY, temporaryDirectory.getAbsolutePath());
    commandParams.put(KerberosServerAction.DEFAULT_REALM, "REALM.COM");
    commandParams.put(KerberosServerAction.KDC_TYPE, KDCType.MIT_KDC.toString());

    expect(mockExecutionCommand.getCommandParams()).andReturn(commandParams).anyTimes();
    expect(mockExecutionCommand.getClusterName()).andReturn("c1").anyTimes();
    expect(mockExecutionCommand.getClusterId()).andReturn("1").anyTimes();
    expect(mockExecutionCommand.getConfigurations()).andReturn(Collections.emptyMap()).anyTimes();
    expect(mockExecutionCommand.getRoleCommand()).andReturn(null).anyTimes();
    expect(mockExecutionCommand.getRole()).andReturn(null).anyTimes();
    expect(mockExecutionCommand.getServiceName()).andReturn(null).anyTimes();
    expect(mockExecutionCommand.getTaskId()).andReturn(1L).anyTimes();

    expect(mockHostRoleCommand.getRequestId()).andReturn(1L).anyTimes();
    expect(mockHostRoleCommand.getStageId()).andReturn(1L).anyTimes();
  }

  @After
  public void tearDown() throws Exception {
    if (temporaryDirectory != null) {
      new File(temporaryDirectory, KerberosIdentityDataFileWriter.DATA_FILE_NAME).delete();
      temporaryDirectory.delete();
    }
  }

  @Test
  public void testGetCommandParameterValueStatic() throws Exception {
    Assert.assertNull(KerberosServerAction.getCommandParameterValue(commandParams, "nonexistingvalue"));
    Assert.assertEquals("REALM.COM", KerberosServerAction.getCommandParameterValue(commandParams, KerberosServerAction.DEFAULT_REALM));
  }

  @Test
  public void testGetDefaultRealmStatic() throws Exception {
    Assert.assertEquals("REALM.COM", KerberosServerAction.getDefaultRealm(commandParams));
  }

  @Test
  public void testGetKDCTypeStatic() throws Exception {
    Assert.assertEquals(KDCType.MIT_KDC, KerberosServerAction.getKDCType(commandParams));
  }

  @Test
  public void testGetDataDirectoryPathStatic() throws Exception {
    Assert.assertEquals(temporaryDirectory.getAbsolutePath(),
        KerberosServerAction.getDataDirectoryPath(commandParams));
  }

  @Test
  public void testSetPrincipalPasswordMapStatic() throws Exception {
    ConcurrentMap<String, Object> sharedMap = new ConcurrentHashMap<>();
    Map<String, String> dataMap = new HashMap<>();

    KerberosServerAction.setPrincipalPasswordMap(sharedMap, dataMap);
    Assert.assertSame(dataMap, KerberosServerAction.getPrincipalPasswordMap(sharedMap));
  }

  @Test
  public void testGetPrincipalPasswordMapStatic() throws Exception {
    ConcurrentMap<String, Object> sharedMap = new ConcurrentHashMap<>();
    Assert.assertNotNull(KerberosServerAction.getPrincipalPasswordMap(sharedMap));
  }

  @Test
  public void testGetDataDirectoryPath() throws Exception {
    replayAll();
    Assert.assertEquals(temporaryDirectory.getAbsolutePath(), action.getDataDirectoryPath());
    verifyAll();
  }

  @Test
  public void testProcessIdentitiesSuccess() throws Exception {
    KerberosHelper kerberosHelper = injector.getInstance(KerberosHelper.class);
    expect(kerberosHelper.getKDCAdministratorCredentials(anyObject(String.class)))
        .andReturn(new PrincipalKeyCredential("principal", "password"))
        .anyTimes();

    KerberosOperationHandler kerberosOperationHandler = createMock(KerberosOperationHandler.class);
    kerberosOperationHandler.open(anyObject(PrincipalKeyCredential.class), anyString(), anyObject(Map.class));
    expectLastCall().atLeastOnce();
    kerberosOperationHandler.close();
    expectLastCall().atLeastOnce();

    KerberosOperationHandlerFactory factory = injector.getInstance(KerberosOperationHandlerFactory.class);
    expect(factory.getKerberosOperationHandler(KDCType.MIT_KDC)).andReturn(kerberosOperationHandler).once();

    replayAll();

    ConcurrentMap<String, Object> sharedMap = new ConcurrentHashMap<>();
    CommandReport report = action.processIdentities(sharedMap);
    Assert.assertNotNull(report);
    Assert.assertEquals(HostRoleStatus.COMPLETED.toString(), report.getStatus());

    for (Map.Entry<String, Object> entry : sharedMap.entrySet()) {
      Assert.assertEquals(entry.getValue(), entry.getKey());
    }

    verifyAll();
  }

  @Test
  public void testProcessIdentitiesFail() throws Exception {
    KerberosHelper kerberosHelper = injector.getInstance(KerberosHelper.class);
    expect(kerberosHelper.getKDCAdministratorCredentials(anyObject(String.class)))
        .andReturn(new PrincipalKeyCredential("principal", "password"))
        .anyTimes();

    KerberosOperationHandler kerberosOperationHandler = createMock(KerberosOperationHandler.class);
    kerberosOperationHandler.open(anyObject(PrincipalKeyCredential.class), anyString(), anyObject(Map.class));
    expectLastCall().atLeastOnce();
    kerberosOperationHandler.close();
    expectLastCall().atLeastOnce();

    KerberosOperationHandlerFactory factory = injector.getInstance(KerberosOperationHandlerFactory.class);
    expect(factory.getKerberosOperationHandler(KDCType.MIT_KDC)).andReturn(kerberosOperationHandler).anyTimes();

    replayAll();

    ConcurrentMap<String, Object> sharedMap = new ConcurrentHashMap<>();
    sharedMap.put("FAIL", "true");

    CommandReport report = action.processIdentities(sharedMap);
    Assert.assertNotNull(report);
    Assert.assertEquals(HostRoleStatus.FAILED.toString(), report.getStatus());

    verifyAll();
  }

  @Test
  public void testGetConfigurationProperties() throws AmbariException {
    Config emptyConfig = createMock(Config.class);
    expect(emptyConfig.getProperties()).andReturn(Collections.emptyMap()).once();

    Config missingPropertiesConfig = createMock(Config.class);
    expect(missingPropertiesConfig.getProperties()).andReturn(null).once();

    expect(cluster.getDesiredConfigByType("invalid-type")).andReturn(null).once();
    expect(cluster.getDesiredConfigByType("missing-properties-type")).andReturn(missingPropertiesConfig).once();
    expect(cluster.getDesiredConfigByType("empty-type")).andReturn(emptyConfig).once();

    replayAll();

    Assert.assertNull(action.getConfigurationProperties(null));
    Assert.assertNull(action.getConfigurationProperties("invalid-type"));
    Assert.assertNull(action.getConfigurationProperties("missing-properties-type"));
    Assert.assertEquals(Collections.emptyMap(), action.getConfigurationProperties("empty-type"));
    Assert.assertEquals(KERBEROS_ENV_PROPERTIES, action.getConfigurationProperties("kerberos-env"));

    verifyAll();
  }
}
