package org.jboss.resteasy.setup;

import org.jboss.as.arquillian.api.ServerSetupTask;
import org.jboss.as.arquillian.container.ManagementClient;
import org.jboss.resteasy.utils.TestUtil;
import org.wildfly.extras.creaper.commands.security.AddLoginModule;
import org.wildfly.extras.creaper.commands.security.AddSecurityDomain;
import org.wildfly.extras.creaper.core.online.OnlineManagementClient;
import org.wildfly.extras.creaper.core.online.operations.Address;
import org.wildfly.extras.creaper.core.online.operations.Operations;
import org.wildfly.extras.creaper.core.online.operations.Values;
import org.wildfly.extras.creaper.core.online.operations.admin.Administration;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URISyntaxException;

import static org.junit.Assert.assertTrue;

/**
 * This abstract class implements steps needed to create PicketBox or Elytron security domain.
 *
 */
public abstract class AbstractUsersRolesSecurityDomainSetup implements ServerSetupTask {

   // Creaper fields
   private static OnlineManagementClient managementClient;
   private Operations ops;
   private Administration administration;

   // Properties file path
   private static final String USERS_FILENAME = "users.properties";
   private static final String ROLES_FILENAME = "roles.properties";
   private File USERS_FILE;
   private File ROLES_FILE;

   // This property decides under which security subsystem will be used for the tests
   private String subsystem = System.getProperty("security.domain", "picketbox");

   // Security domain name shared by elytron and picketBox configuration
   private String securityDomainName = "jaxrsSecDomain";

   // PicketBox related settings
   private Address PICKETBOX_SECURITY_DOMAIN_ADDRESS
         = Address.subsystem("security").and("security-domain", securityDomainName);
   private Address PICKETBOX_AUTHN_CLASSIC_ADDRESS = PICKETBOX_SECURITY_DOMAIN_ADDRESS
         .and("authentication", "classic");
   private static final String PICKETBOX_LOGIN_MODULE_NAME = "UsersRoles";
   private Address PICKETBOX_LOGIN_MODULE_ADDRESS = PICKETBOX_AUTHN_CLASSIC_ADDRESS
         .and("login-module", PICKETBOX_LOGIN_MODULE_NAME);

   // Elytron related settings
   private static final String ELYTRON_PROPERTIES_REALM_NAME = "propRealm";
   private static final Address ELYTRON_PROPERTIES_REALM_ADDRESS
         = Address.subsystem("elytron").and("properties-realm", ELYTRON_PROPERTIES_REALM_NAME);
   private static final String ELYTRON_SECURITY_DOMAIN_NAME = "propertyElytronSecDomain";
   private static final Address ELYTRON_SECURITY_DOMAIN_ADDRESS
         = Address.subsystem("elytron").and("security-domain", ELYTRON_SECURITY_DOMAIN_NAME);
   private static final String ELYTRON_PROP_HTTP_AUTHENTICATION_FACTORY_NAME = "prop-http-authentication-factory";
   private static final Address ELYTRON_PROP_HTTP_AUTHENTICATION_FACTORY_ADDRESS
         = Address.subsystem("elytron").and("http-authentication-factory", ELYTRON_PROP_HTTP_AUTHENTICATION_FACTORY_NAME);
   private String UNDERTOW_APPLICATION_SECURITY_DOMAIN_NAME = securityDomainName;
   private Address UNDERTOW_APPLICATION_SECURITY_DOMAIN_ADDRESS
         = Address.subsystem("undertow").and("application-security-domain", UNDERTOW_APPLICATION_SECURITY_DOMAIN_NAME);

   /**
    * Set security subsystem
    * @param subsystem
    */
   public void setSubsystem(String subsystem) {
      this.subsystem = subsystem;
   }

   /**
    * Set security domain name related configuration
    * @param securityDomainName
    */
   public void setSecurityDomainName(String securityDomainName) {
      this.securityDomainName = securityDomainName;
      this.PICKETBOX_SECURITY_DOMAIN_ADDRESS=Address.subsystem("security").and("security-domain", securityDomainName);
      this.UNDERTOW_APPLICATION_SECURITY_DOMAIN_NAME=securityDomainName;
      this.UNDERTOW_APPLICATION_SECURITY_DOMAIN_ADDRESS
            = Address.subsystem("undertow").and("application-security-domain", UNDERTOW_APPLICATION_SECURITY_DOMAIN_NAME);
   }

   /**
    * Creates Files pointing to users.properties and roles.properties for the current test.
    * @param folder
    */
   public void createPropertiesFiles(File folder) {
      this.USERS_FILE = new File(folder, USERS_FILENAME);
      this.ROLES_FILE = new File(folder, ROLES_FILENAME);
   }

   @Override
   public void setup(ManagementClient fakemanagementClient, String s) throws Exception {

      // Set path for users.properties and roles.properties
      setConfigurationPath();

      // Create and initialize management client
      managementClient = TestUtil.clientInit();
      administration = new Administration(managementClient);
      ops = new Operations(managementClient);

      if (subsystem.equals("elytron")) {
         configureElytron();
      } else {
         configurePicketBox();
      }
   }

   @Override
   public void tearDown(ManagementClient fakemanagementClient, String s) throws Exception {

      if (subsystem.equals("elytron")) {
         cleanUpElytron();
      } else {
         cleanUpPicketBox();
      }
   }

   /**
    * Set necessary test related paths
    */
   public abstract void setConfigurationPath() throws URISyntaxException, MalformedURLException;

   /**
    * Creates Elytron security domain
    * @throws Exception
    */
   private void configureElytron() throws Exception {

      // Note: This complicated setting may be simplified once WFLY-7949 is resolved

      // Create Elytron properties-realm
      ops.add(ELYTRON_PROPERTIES_REALM_ADDRESS, Values.empty()
            .andObject("users-properties", Values.empty()
                  .and("path", USERS_FILE.getAbsolutePath())
                  .andOptional("plain-text", true))
            .andObjectOptional("groups-properties", Values.empty()
               .and("path", ROLES_FILE.getAbsolutePath())));

      administration.reloadIfRequired();

      // Create Elytron security-domain
      managementClient.executeCli("/subsystem=elytron/security-domain="
            + ELYTRON_SECURITY_DOMAIN_NAME
            + ":add(realms=[{realm="
            + ELYTRON_PROPERTIES_REALM_NAME + ",role-decoder=groups-to-roles}],default-realm=propRealm,permission-mapper=default-permission-mapper)");

      // Create Elytron http-authentication-factory with previous security-domain
      managementClient.executeCli("/subsystem=elytron/http-authentication-factory="
            + ELYTRON_PROP_HTTP_AUTHENTICATION_FACTORY_NAME + ":add(http-server-mechanism-factory=global,security-domain="
            + ELYTRON_SECURITY_DOMAIN_NAME
            + ",mechanism-configurations=[{mechanism-name=BASIC,mechanism-realm-configurations=[{realm-name=\"Property Elytron\"}]}])");

      // Set undertow application-security-domain to the custom http-authentication-factory
      managementClient.executeCli("/subsystem=undertow/application-security-domain="
            + securityDomainName + ":add(http-authentication-factory="
            +  ELYTRON_PROP_HTTP_AUTHENTICATION_FACTORY_NAME + ")");

      administration.reloadIfRequired();

      assertTrue("The elytron/properties-realm should be created", ops.exists(ELYTRON_PROPERTIES_REALM_ADDRESS));
      assertTrue("The elytron/security-domain should be created", ops.exists(ELYTRON_SECURITY_DOMAIN_ADDRESS));
      assertTrue("The elytron/http-authentication-factory should be created", ops.exists(ELYTRON_PROP_HTTP_AUTHENTICATION_FACTORY_ADDRESS));
      assertTrue("The undertow/application-security-domain should be created", ops.exists(UNDERTOW_APPLICATION_SECURITY_DOMAIN_ADDRESS));
   }

   /**
    * Creates PicketBox security domain
    * @throws Exception
    */
   private void configurePicketBox() throws Exception {

      // Create security domain
      AddSecurityDomain addSecurityDomain = new AddSecurityDomain.Builder(securityDomainName).build();
      managementClient.apply(addSecurityDomain);

      // Create login module
      AddLoginModule addLoginModule = new AddLoginModule.Builder("org.jboss.security.auth.spi.UsersRolesLoginModule",
            PICKETBOX_LOGIN_MODULE_NAME)
            .securityDomainName(securityDomainName)
            .flag("required")
            .module("org.picketbox")
            .addModuleOption("usersProperties", USERS_FILE.getAbsolutePath())
            .addModuleOption("rolesProperties", ROLES_FILE.getAbsolutePath())
            .build();

      managementClient.apply(addLoginModule);

      administration.reloadIfRequired();

      assertTrue("The login module should be created", ops.exists(PICKETBOX_LOGIN_MODULE_ADDRESS));
   }

   /**
    * Reverts all configuration done for Elytron
    * @throws Exception
    */
   private void cleanUpElytron() throws Exception {
      try {
         ops.removeIfExists(UNDERTOW_APPLICATION_SECURITY_DOMAIN_ADDRESS);
         ops.removeIfExists(ELYTRON_PROP_HTTP_AUTHENTICATION_FACTORY_ADDRESS);
         ops.removeIfExists(ELYTRON_SECURITY_DOMAIN_ADDRESS);
         ops.removeIfExists(ELYTRON_PROPERTIES_REALM_ADDRESS);
         administration.reloadIfRequired();
      } finally {
         managementClient.close();
      }
   }

   /**
    * Reverts all configuration done for PicketBox
    * @throws Exception
    */
   private void cleanUpPicketBox() throws Exception {
      try {
         ops.removeIfExists(PICKETBOX_SECURITY_DOMAIN_ADDRESS);
         administration.reloadIfRequired();
      } finally {
         managementClient.close();
      }
   }
}
