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();
        }
    }
}
