/*
Copyright 2017 Red Hat, Inc.

Licensed 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.wildfly.extension.elytron;

import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NAME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OPERATION_HEADERS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OPERATION_REQUIRES_RELOAD;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OPERATION_REQUIRES_RESTART;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_RESOURCE_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESPONSE_HEADERS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ROLLBACK_ON_RUNTIME_FAILURE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.VALUE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.io.IOException;

import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.as.subsystem.test.AbstractSubsystemBaseTest;
import org.jboss.as.subsystem.test.KernelServices;
import org.jboss.dmr.ModelNode;
import org.junit.Test;

/**
 * Tests handling of the policy=* resource.
 *
 * @author Brian Stansberry
 */
public class PoliciesTestCase extends AbstractSubsystemBaseTest {

    private static final ModelNode JACC_POLICY;
    private static final ModelNode CUSTOM_POLICY;

    static {
        ModelNode policy = new ModelNode();
        policy.get("class-name").set("a.b.c.CustomPolicy");
        policy.get("module").set("a.b.c.custom");
        CUSTOM_POLICY = policy;
        policy = new ModelNode();
        policy.get("policy").set("a.b.c.Policy");
        policy.get("configuration-factory").set("a.b.PolicyConfigurationFactory");
        policy.get("module").set("a.b");
        JACC_POLICY = policy;
    }

    public PoliciesTestCase() {
        super(ElytronExtension.SUBSYSTEM_NAME, new ElytronExtension());
    }

    @Override
    protected String getSubsystemXml() throws IOException {
        return readResource("jacc-provider.xml");
    }

    @Override
    public void testSubsystem() throws Exception {
        KernelServices services = standardSubsystemTest(null, true);

        // Check no default-policy
        checkNoDefaultPolicy(services, "elytron-a");

        // Check alternatives and list parameter correction
        checkPolicyAttributes(services, true);
    }

    @Test
    public void testParseAndMarshalModel_JaccWithProviders() throws Exception {
        // Here we know that unused elements from the original xml will be dropped (see WFCORE-3041).
        // So we don't want to compare what we persist to that original. But we do want to compare
        // to a model parsed from an equivalent config that does not have extraneous elements
        standardSubsystemTest("jacc-with-providers.xml", "jacc-provider.xml",false);
    }

    // Skip this variant as we use this config in the basic testSubsystem
//    @Test
//    public void testParseAndMarshalModel_Jacc() throws Exception {
//        standardSubsystemTest("jacc-provider.xml");
//    }

    @Test
    public void testParseAndMarshalModel_CustomPolicies() throws Exception {
        // Here we know that unused elements from the original xml will be dropped (see WFCORE-3041).
        // So we don't want to compare what we persist to that original. But we do want to compare
        // to a model parsed from an equivalent config that does not have extraneous elements
        standardSubsystemTest("custom-policies.xml", "custom-policy.xml",false);
    }

    @Test
    public void testParseAndMarshalModel_CustomPolicy() throws Exception {
        KernelServices services = standardSubsystemTest("custom-policy.xml", true);

        // Check no default-policy
        checkNoDefaultPolicy(services, "custom-b");

        // Check alternatives and list parameter correction
        checkPolicyAttributes(services, false);
    }

    @Test
    public void testDefaultPolicyWriteIgnored() throws Exception {
        KernelServices services = standardSubsystemTest("custom-policy.xml", true);
        ModelNode write = Util.getWriteAttributeOperation(getPolicyAddress("custom-b"), "default-policy", "test");
        ModelNode response = services.executeOperation(write);
        assertEquals(response.toString(), "success", response.get("outcome").asString());
        assertFalse(response.toString(), response.has(RESPONSE_HEADERS, OPERATION_REQUIRES_RELOAD));
        assertFalse(response.toString(), response.has(RESPONSE_HEADERS, OPERATION_REQUIRES_RESTART));
        checkNoDefaultPolicy(services, "custom-b");
    }

    private static PathAddress getPolicyAddress(String policy) {
        return PathAddress.pathAddress(SUBSYSTEM, ElytronExtension.SUBSYSTEM_NAME).append("policy", policy);
    }

    private static void checkNoDefaultPolicy(KernelServices services, String policy) throws OperationFailedException {
        ModelNode result = services.executeForResult(Util.createEmptyOperation(READ_RESOURCE_OPERATION,
                getPolicyAddress(policy)));
        assertTrue(result.toString(), result.has("default-policy"));
        assertFalse(result.toString(), result.hasDefined("default-policy"));
    }

    private static void checkPolicyAttributes(KernelServices services, boolean forJACC) throws OperationFailedException {
        PathAddress address = forJACC ? getPolicyAddress("elytron-a")  : getPolicyAddress("custom-b");
        String attribute= forJACC ? "jacc-policy" : "custom-policy";
        ModelNode policy = forJACC ?  JACC_POLICY : CUSTOM_POLICY;
        String alternative = forJACC ? "custom-policy" : "jacc-policy";
        ModelNode alternativePolicy = forJACC ? CUSTOM_POLICY : JACC_POLICY;

        // Check alternatives
        ModelNode write = Util.getWriteAttributeOperation(address, alternative, alternativePolicy);
        write.get(OPERATION_HEADERS, ROLLBACK_ON_RUNTIME_FAILURE).set(false);

        services.executeForFailure(write);

        // Check parameter correction with a single element list
        ModelNode list = new ModelNode();
        list.add(policy);
        write.get(VALUE).set(list);
        write.get(NAME).set(attribute);

        services.executeForResult(write);

        ModelNode value = services.executeForResult(Util.getReadAttributeOperation(address, attribute));
        assertEquals(policy, value);

        // Check parameter not corrected with a multiple element list
        list.add(policy); // just be lazy reuse the same element
        write.get(VALUE).set(list);
        services.executeForFailure(write);

        value = services.executeForResult(Util.getReadAttributeOperation(address, attribute));
        assertEquals(policy, value);
    }
}
