package org.infinispan.test.integration.security.utils;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.text.StrSubstitutor;
import org.apache.directory.api.ldap.model.constants.SupportedSaslMechanisms;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.ldif.LdifEntry;
import org.apache.directory.api.ldap.model.ldif.LdifReader;
import org.apache.directory.api.ldap.model.schema.SchemaManager;
import org.apache.directory.server.annotations.CreateKdcServer;
import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.annotations.CreateTransport;
import org.apache.directory.server.annotations.SaslMechanism;
import org.apache.directory.server.core.annotations.AnnotationUtils;
import org.apache.directory.server.core.annotations.ContextEntry;
import org.apache.directory.server.core.annotations.CreateDS;
import org.apache.directory.server.core.annotations.CreateIndex;
import org.apache.directory.server.core.annotations.CreatePartition;
import org.apache.directory.server.core.api.DirectoryService;
import org.apache.directory.server.core.factory.DSAnnotationProcessor;
import org.apache.directory.server.core.kerberos.KeyDerivationInterceptor;
import org.apache.directory.server.factory.ServerAnnotationProcessor;
import org.apache.directory.server.kerberos.kdc.KdcServer;
import org.apache.directory.server.ldap.LdapServer;
import org.apache.directory.server.ldap.handlers.sasl.cramMD5.CramMd5MechanismHandler;
import org.apache.directory.server.ldap.handlers.sasl.digestMD5.DigestMd5MechanismHandler;
import org.apache.directory.server.ldap.handlers.sasl.gssapi.GssapiMechanismHandler;
import org.apache.directory.server.ldap.handlers.sasl.ntlm.NtlmMechanismHandler;
import org.apache.directory.server.ldap.handlers.sasl.plain.PlainMechanismHandler;
import org.infinispan.commons.logging.Log;
import org.infinispan.commons.logging.LogFactory;

/**
 * @author <a href="mailto:vjuranek@redhat.com">Vojtech Juranek</a>
 * @since 7.0
 */
public class ApacheDsKrbLdap {
   public static final int LDAP_PORT = 10389;
   public static final int KERBEROS_PORT = 6088;
   public static final String KERBEROS_PRIMARY_REALM = "INFINISPAN.ORG";
   public static final String LDAP_INIT_FILE = "ldif/ispn-krb-test.ldif";
   public static final String BASE_DN = "dc=infinispan,dc=org";

   private static Log log = LogFactory.getLog(ApacheDsKrbLdap.class);
   private DirectoryService directoryService;
   private LdapServer ldapServer;
   private KdcServer kdcServer;

   public ApacheDsKrbLdap(String hostname) throws Exception {
      createDs();
      createKdc();
      createLdap(hostname);
   }

   public void start() throws Exception {
      ldapServer.start();
   }

   public void stop() throws Exception {
      kdcServer.stop();
      ldapServer.stop();
      directoryService.shutdown();
      FileUtils.deleteDirectory(directoryService.getInstanceLayout().getInstanceDirectory());
   }

   @CreateDS(
         name = "InfinispanDS",
         partitions = {
               @CreatePartition(
                     name = "infinispan",
                     suffix = BASE_DN,
                     contextEntry = @ContextEntry(
                           entryLdif =
                                 "dn: " + BASE_DN + "\n" +
                                       "dc: infinispan\n" +
                                       "objectClass: top\n" +
                                       "objectClass: domain\n\n"),
                     indexes = {
                           @CreateIndex(attribute = "objectClass"),
                           @CreateIndex(attribute = "dc"),
                           @CreateIndex(attribute = "ou")
                     }
               )
         },
         additionalInterceptors = {
               KeyDerivationInterceptor.class
         }
   )
   public void createDs() throws Exception {
      directoryService = DSAnnotationProcessor.getDirectoryService();
   }

   @CreateKdcServer(
         primaryRealm = KERBEROS_PRIMARY_REALM,
         kdcPrincipal = "krbtgt/" + KERBEROS_PRIMARY_REALM + "@" + KERBEROS_PRIMARY_REALM,
         searchBaseDn = BASE_DN,
         transports = {@CreateTransport(protocol = "UDP", port = KERBEROS_PORT)}
   )
   public void createKdc() throws Exception {
      kdcServer = KdcServerAnnotationProcessor.getKdcServer(directoryService);
   }

   @CreateLdapServer(
         transports = {@CreateTransport(protocol = "LDAP", port = LDAP_PORT)},
         saslRealms = {KERBEROS_PRIMARY_REALM},
         saslMechanisms = {
               @SaslMechanism(name = SupportedSaslMechanisms.GSSAPI, implClass = GssapiMechanismHandler.class),
               @SaslMechanism(name = SupportedSaslMechanisms.PLAIN, implClass = PlainMechanismHandler.class),
               @SaslMechanism(name = SupportedSaslMechanisms.CRAM_MD5, implClass = CramMd5MechanismHandler.class),
               @SaslMechanism(name = SupportedSaslMechanisms.DIGEST_MD5, implClass = DigestMd5MechanismHandler.class),
               @SaslMechanism(name = SupportedSaslMechanisms.NTLM, implClass = NtlmMechanismHandler.class),
               @SaslMechanism(name = SupportedSaslMechanisms.GSS_SPNEGO, implClass = NtlmMechanismHandler.class)
         }
   )
   public void createLdap(final String hostname) throws Exception {
      final String initFile = System.getProperty("ldap.init.file", LDAP_INIT_FILE);
      final String ldifContent = IOUtils.toString(getClass().getClassLoader().getResource(initFile), "UTF-8");

      final Map<String, String> map = new HashMap<String, String>();
      map.put("hostname", hostname);

      final SchemaManager schemaManager = directoryService.getSchemaManager();
      try {
         for (LdifEntry ldifEntry : new LdifReader(IOUtils.toInputStream(StrSubstitutor.replace(ldifContent, map)))) {
            directoryService.getAdminSession().add(new DefaultEntry(schemaManager, ldifEntry.getEntry()));
         }
      } catch (Exception e) {
         log.error("Error adding ldif entries", e);
         throw e;
      }
      final CreateLdapServer createLdapServer = (CreateLdapServer) AnnotationUtils.getInstance(CreateLdapServer.class);
      ldapServer = ServerAnnotationProcessor.instantiateLdapServer(createLdapServer, directoryService);
      ldapServer.setSearchBaseDn(BASE_DN);
      ldapServer.setSaslHost(hostname);
      ldapServer.setSaslPrincipal("ldap/" + hostname + "@" + KERBEROS_PRIMARY_REALM);
   }
}
