/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.brooklyn.entity.brooklynnode;

import java.net.URI;
import java.util.concurrent.Callable;

import org.apache.brooklyn.core.entity.EntityAsserts;
import org.apache.brooklyn.test.HttpTestUtils;
import org.apache.brooklyn.util.collections.Jsonya;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.collections.MutableSet;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.http.HttpTool;
import org.apache.brooklyn.util.http.HttpToolResponse;
import org.apache.brooklyn.util.net.Urls;
import org.apache.brooklyn.util.repeat.Repeater;
import org.apache.brooklyn.util.time.Duration;
import org.apache.http.client.HttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.Test;
import org.apache.brooklyn.api.entity.Application;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.api.mgmt.Task;
import org.apache.brooklyn.core.config.BasicConfigKey;
import org.apache.brooklyn.core.entity.Attributes;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.core.entity.factory.ApplicationBuilder;
import org.apache.brooklyn.core.test.entity.TestApplication;
import org.apache.brooklyn.core.test.entity.TestEntity;
import org.apache.brooklyn.launcher.SimpleYamlLauncherForTests;
import org.apache.brooklyn.launcher.camp.SimpleYamlLauncher;

import com.google.common.collect.Iterables;

/** REST-accessible extension of {@link BrooklynNodeTest} */
public class BrooklynNodeRestTest {

    private static final Logger log = LoggerFactory.getLogger(BrooklynNodeRestTest.class);
    
    // takes a while when run on its own, because initializing war and making some requests;
    // but there are no waits (beyond 10ms), the delay is all classloading;
    // and this tests a lot of things, REST API, Brooklyn Node, yaml deployment,
    // so feels worth it to have as a unit test
    // FIXME[BROOKLYN-43]: Test fails if security is configured in brooklyn.properties.
    @Test(groups = "WIP")
    public void testBrooklynNodeRestDeployAndMirror() {
        final SimpleYamlLauncher l = new SimpleYamlLauncherForTests();
        try {
            TestApplication app = ApplicationBuilder.newManagedApp(TestApplication.class, l.getManagementContext());

            BrooklynNode bn = app.createAndManageChild(EntitySpec.create(BrooklynNode.class, SameBrooklynNodeImpl.class));
            bn.start(MutableSet.<Location>of());
            
            URI uri = bn.getAttribute(BrooklynNode.WEB_CONSOLE_URI);
            Assert.assertNotNull(uri);
            EntityAsserts.assertAttributeEqualsEventually(bn, Attributes.SERVICE_UP, true);
            log.info("Created BrooklynNode: "+bn);

            // deploy
            Task<?> t = bn.invoke(BrooklynNode.DEPLOY_BLUEPRINT, ConfigBag.newInstance()
                .configure(BrooklynNode.DeployBlueprintEffector.BLUEPRINT_TYPE, TestApplication.class.getName())
                .configure(BrooklynNode.DeployBlueprintEffector.BLUEPRINT_CONFIG, MutableMap.<String,Object>of("x", 1, "y", "Y"))
                .getAllConfig());
            log.info("Deployment result: "+t.getUnchecked());
            
            MutableSet<Application> apps = MutableSet.copyOf( l.getManagementContext().getApplications() );
            Assert.assertEquals(apps.size(), 2);
            apps.remove(app);
            
            Application newApp = Iterables.getOnlyElement(apps);
            Entities.dumpInfo(newApp);
            
            Assert.assertEquals(newApp.getConfig(new BasicConfigKey<Integer>(Integer.class, "x")), (Integer)1);
            
            // check mirror
            String newAppId = newApp.getId();
            BrooklynEntityMirror mirror = app.createAndManageChild(EntitySpec.create(BrooklynEntityMirror.class)
                .configure(BrooklynEntityMirror.MIRRORED_ENTITY_URL, 
                    Urls.mergePaths(uri.toString(), "/v1/applications/"+newAppId+"/entities/"+newAppId))
                .configure(BrooklynEntityMirror.MIRRORED_ENTITY_ID, newAppId)
                .configure(BrooklynEntityMirror.POLL_PERIOD, Duration.millis(10)));
            
            Entities.dumpInfo(mirror);
            
            EntityAsserts.assertAttributeEqualsEventually(mirror, Attributes.SERVICE_UP, true);
            
            ((EntityInternal)newApp).sensors().set(TestEntity.NAME, "foo");
            EntityAsserts.assertAttributeEqualsEventually(mirror, TestEntity.NAME, "foo");
            log.info("Mirror successfully validated");
            
            // also try deploying by invoking deploy through json
            // (catch issues when effector params are map)
            HttpClient client = HttpTool.httpClientBuilder().build();
            HttpToolResponse result = HttpTool.httpPost(client, URI.create(Urls.mergePaths(uri.toString(), "/v1/applications/"+app.getId()+"/entities/"+bn.getId()
                    +"/effectors/deployBlueprint")), 
                MutableMap.of(com.google.common.net.HttpHeaders.CONTENT_TYPE, "application/json"), 
                Jsonya.newInstance()
                    .put("blueprintType", TestApplication.class.getName())
                    .put("blueprintConfig", MutableMap.of(TestEntity.CONF_NAME.getName(), "foo"))
                .toString().getBytes());
            log.info("Deploy effector invoked, result: "+result);
            HttpTestUtils.assertHealthyStatusCode( result.getResponseCode() );
            
            Repeater.create().every(Duration.millis(10)).until(new Callable<Boolean>() {
                @Override
                public Boolean call() throws Exception {
                    return l.getManagementContext().getApplications().size() == 3;
                }
            }).limitTimeTo(Duration.TEN_SECONDS).runRequiringTrue();
            
            apps = MutableSet.copyOf( l.getManagementContext().getApplications() );
            apps.removeAll( MutableSet.of(app, newApp) );
            Application newApp2 = Iterables.getOnlyElement(apps);
            Entities.dumpInfo(newApp2);
            
            EntityAsserts.assertAttributeEqualsEventually(newApp2, Attributes.SERVICE_UP, true);
            Assert.assertEquals(newApp2.getConfig(TestEntity.CONF_NAME), "foo");
            
        } finally {
            l.destroyAll();
        }
        log.info("DONE");
    }
}
