/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package org.jboss.as.controller.transform.description;

import java.util.Collections;

import org.jboss.as.controller.ExpressionResolver;
import org.jboss.as.controller.ModelVersion;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.ProcessType;
import org.jboss.as.controller.ResourceDefinition;
import org.jboss.as.controller.RunningMode;
import org.jboss.as.controller.SimpleResourceDefinition;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.descriptions.NonResolvingResourceDescriptionResolver;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.as.controller.registry.Resource;
import org.jboss.as.controller.transform.OperationTransformer;
import org.jboss.as.controller.transform.OperationTransformer.TransformedOperation;
import org.jboss.as.controller.transform.ResourceTransformationContext;
import org.jboss.as.controller.transform.TransformationContext;
import org.jboss.as.controller.transform.TransformationTarget;
import org.jboss.as.controller.transform.TransformationTargetImpl;
import org.jboss.as.controller.transform.TransformerRegistry;
import org.jboss.as.controller.transform.Transformers;
import org.jboss.as.controller.transform.TransformersSubRegistration;
import org.jboss.dmr.ModelNode;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

/**
 * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
 */
public class RecursiveDiscardAndRemoveTestCase {

    private static PathElement PATH = PathElement.pathElement("toto", "testSubsystem");
    private static PathElement DISCARDED_WILDCARD = PathElement.pathElement("removeall");
    private static PathElement DISCARDED_WILDCARD_ENTRY = PathElement.pathElement("removeall", "entry");
    private static PathElement DISCARDED_SPECIFIC = PathElement.pathElement("remove", "one");
    private static PathElement DISCARDED_CHILD = PathElement.pathElement("child", "one");

    private Resource resourceRoot = Resource.Factory.create();
    private TransformerRegistry registry = TransformerRegistry.Factory.create();
    private ManagementResourceRegistration resourceRegistration = ManagementResourceRegistration.Factory.forProcessType(ProcessType.EMBEDDED_SERVER).createRegistration(ROOT);
    private TransformersSubRegistration transformersSubRegistration;
    private ModelNode resourceModel;

    @Before
    public void setUp() {
        // Cleanup
        resourceRoot = Resource.Factory.create();
        registry = TransformerRegistry.Factory.create();
        resourceRegistration = ManagementResourceRegistration.Factory.forProcessType(ProcessType.EMBEDDED_SERVER).createRegistration(ROOT);
        // test
        final Resource toto = Resource.Factory.create();
        resourceRoot.registerChild(PATH, toto);
        resourceModel = toto.getModel();
        resourceModel.get("subs").set("test");

        Resource wildcard = Resource.Factory.create();
        toto.registerChild(DISCARDED_WILDCARD_ENTRY, wildcard);
        wildcard.getModel().get("wild").set("test");

        Resource wildcardChild = Resource.Factory.create();
        wildcard.registerChild(DISCARDED_CHILD, wildcardChild);
        wildcardChild.getModel().get("wildchild").set("test");

        Resource specific = Resource.Factory.create();
        toto.registerChild(DISCARDED_SPECIFIC, specific);
        specific.getModel().get("spec").set("test");

        Resource specificChild = Resource.Factory.create();
        specific.registerChild(DISCARDED_CHILD, specificChild);
        specificChild.getModel().get("specchild").set("test");

        // Register the description
        transformersSubRegistration = registry.getServerRegistration(ModelVersion.create(1));
    }


    @Test
    public void testDiscardResource() throws Exception {
        final ResourceTransformationDescriptionBuilder builder = TransformationDescriptionBuilder.Factory.createInstance(PATH);
        builder.discardChildResource(DISCARDED_WILDCARD);
        builder.discardChildResource(DISCARDED_SPECIFIC);
        TransformationDescription.Tools.register(builder.build(), transformersSubRegistration);

        //Make sure the resource has none of the discarded children
        final Resource resource = transformResource();
        Assert.assertNotNull(resource);
        final Resource toto = resource.getChild(PATH);
        Assert.assertNotNull(toto);
        final ModelNode model = toto.getModel();
        Assert.assertEquals(1, model.keys().size());
        Assert.assertEquals("test", model.get("subs").asString());

        //Sanity check that the subsystem works
        sanityTestNonDiscardedResource();

        //Check that the op gets discarded for the wildcard entry
        discardResourceOperationsTest(PathAddress.pathAddress(PATH, DISCARDED_WILDCARD_ENTRY));

        //Check that the op gets discarded for the specific entry
        discardResourceOperationsTest(PathAddress.pathAddress(PATH, DISCARDED_SPECIFIC));

        //Check that the op gets discarded for the wildcard entry child
        discardResourceOperationsTest(PathAddress.pathAddress(PATH, DISCARDED_WILDCARD_ENTRY, DISCARDED_CHILD));

        //Check that the op gets discarded for the specific entry child
        discardResourceOperationsTest(PathAddress.pathAddress(PATH, DISCARDED_SPECIFIC, DISCARDED_CHILD));
    }

    @Test
    public void testRejectResource() throws Exception {
        final ResourceTransformationDescriptionBuilder builder = TransformationDescriptionBuilder.Factory.createInstance(PATH);
        builder.rejectChildResource(DISCARDED_WILDCARD);
        builder.rejectChildResource(DISCARDED_SPECIFIC);
        TransformationDescription.Tools.register(builder.build(), transformersSubRegistration);

        //Make sure the resource has none of the discarded children
        final Resource resource = transformResource();
        Assert.assertNotNull(resource);
        final Resource toto = resource.getChild(PATH);
        Assert.assertNotNull(toto);
        final ModelNode model = toto.getModel();
        Assert.assertEquals(1, model.keys().size());
        Assert.assertEquals("test", model.get("subs").asString());

        //Sanity check that the subsystem works
        sanityTestNonDiscardedResource();

        //Check that the op gets rejected for the wildcard entry
        rejectResourceOperationsTest(PathAddress.pathAddress(PATH, DISCARDED_WILDCARD_ENTRY));

        //Check that the op gets rejected for the specific entry
        rejectResourceOperationsTest(PathAddress.pathAddress(PATH, DISCARDED_SPECIFIC));

        //Check that the op gets rejected for the wildcard entry child
        rejectResourceOperationsTest(PathAddress.pathAddress(PATH, DISCARDED_WILDCARD_ENTRY, DISCARDED_CHILD));

        //Check that the op gets rejected for the specific entry child
        rejectResourceOperationsTest(PathAddress.pathAddress(PATH, DISCARDED_SPECIFIC, DISCARDED_CHILD));
    }

    private void sanityTestNonDiscardedResource() throws Exception {
        ModelNode op = Util.createAddOperation(PathAddress.pathAddress(PATH));
        TransformedOperation transformed = transformOperation(op);
        Assert.assertEquals(op, transformed.getTransformedOperation());
        Assert.assertFalse(transformed.rejectOperation(success()));
        Assert.assertNull(transformed.getFailureDescription());

        op = Util.getWriteAttributeOperation(PathAddress.pathAddress(PATH), "test", new ModelNode("a"));
        transformed = transformOperation(op);
        Assert.assertEquals(op, transformed.getTransformedOperation());
        Assert.assertFalse(transformed.rejectOperation(success()));
        Assert.assertNull(transformed.getFailureDescription());

        op = Util.getUndefineAttributeOperation(PathAddress.pathAddress(PATH), "test");
        transformed = transformOperation(op);
        Assert.assertEquals(op, transformed.getTransformedOperation());
        Assert.assertFalse(transformed.rejectOperation(success()));
        Assert.assertNull(transformed.getFailureDescription());
    }

    private void discardResourceOperationsTest(PathAddress pathAddress) throws Exception {
        ModelNode op = Util.createAddOperation(pathAddress);
        TransformedOperation transformed = transformOperation(op);
        assertDiscarded(transformed);

        op = Util.getWriteAttributeOperation(pathAddress, "test", new ModelNode("a"));
        transformed = transformOperation(op);
        assertDiscarded(transformed);

        op = Util.getUndefineAttributeOperation(pathAddress, "test");
        transformed = transformOperation(op);
        assertDiscarded(transformed);
    }

    private void rejectResourceOperationsTest(PathAddress pathAddress) throws Exception {
        ModelNode op = Util.createAddOperation(pathAddress);
        TransformedOperation transformed = transformOperation(op);
        assertRejected(op, transformed);

        op = Util.getWriteAttributeOperation(pathAddress, "test", new ModelNode("a"));
        transformed = transformOperation(op);
        assertRejected(op, transformed);

        op = Util.getUndefineAttributeOperation(pathAddress, "test");
        transformed = transformOperation(op);
        assertRejected(op, transformed);
    }

    private Resource transformResource() throws OperationFailedException {
        final TransformationTarget target = create(registry, ModelVersion.create(1));
        final ResourceTransformationContext context = createContext(target);
        return getTransfomers(target).transformResource(context, resourceRoot);
    }

    private OperationTransformer.TransformedOperation transformOperation(final ModelNode operation) throws OperationFailedException {
        final TransformationTarget target = create(registry, ModelVersion.create(1));
        final TransformationContext context = createContext(target);
        return getTransfomers(target).transformOperation(context, operation);
    }

    private ResourceTransformationContext createContext(final TransformationTarget target) {
        return Transformers.Factory.create(target, resourceRoot, resourceRegistration,
                ExpressionResolver.TEST_RESOLVER, RunningMode.NORMAL, ProcessType.STANDALONE_SERVER, null);
    }

    private Transformers getTransfomers(final TransformationTarget target) {
        return Transformers.Factory.create(target);
    }

    protected TransformationTarget create(final TransformerRegistry registry, ModelVersion version) {
        return create(registry, version, TransformationTarget.TransformationTargetType.SERVER);
    }

    protected TransformationTarget create(final TransformerRegistry registry, ModelVersion version, TransformationTarget.TransformationTargetType type) {
        return TransformationTargetImpl.create(null, registry, version, Collections.<PathAddress, ModelVersion>emptyMap(), type);
    }

    private static final ResourceDefinition ROOT = new SimpleResourceDefinition(PathElement.pathElement("test"), NonResolvingResourceDescriptionResolver.INSTANCE);

    protected void assertRejected(final ModelNode original, final TransformedOperation transformed) {
        Assert.assertNotNull(transformed);
        final ModelNode operation = transformed.getTransformedOperation();
        Assert.assertNotNull(operation);
        Assert.assertEquals(original, operation);
        Assert.assertTrue(original.get(ModelDescriptionConstants.OP_ADDR).equals(operation.get(ModelDescriptionConstants.OP_ADDR)));
        Assert.assertTrue(transformed.rejectOperation(success()));
        Assert.assertNotNull(transformed.getFailureDescription());
    }

    private void assertDiscarded(final TransformedOperation transformed) {
        Assert.assertNull(transformed.getTransformedOperation());
        Assert.assertFalse(transformed.rejectOperation(success()));
        Assert.assertNull(transformed.getFailureDescription());
    }

    private static ModelNode success() {
        final ModelNode result = new ModelNode();
        result.get(ModelDescriptionConstants.OUTCOME).set(ModelDescriptionConstants.SUCCESS);
        result.get(ModelDescriptionConstants.RESULT);
        return result;
    }
}
