/*
 * 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.rest.client;

import static org.testng.Assert.assertEquals;

import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;

import javax.ws.rs.core.Response;

import org.apache.brooklyn.api.entity.Application;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.core.BrooklynVersion;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.entity.StartableApplication;
import org.apache.brooklyn.core.location.BasicLocationRegistry;
import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
import org.apache.brooklyn.core.test.entity.TestEntity;
import org.apache.brooklyn.rest.BrooklynRestApiLauncher;
import org.apache.brooklyn.rest.BrooklynRestApiLauncherTest;
import org.apache.brooklyn.rest.domain.ApplicationSummary;
import org.apache.brooklyn.rest.domain.CatalogLocationSummary;
import org.apache.brooklyn.rest.security.provider.TestSecurityProvider;
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.util.http.HttpAsserts;
import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

@Test
public class BrooklynApiRestClientTest {

    private static final Logger log = LoggerFactory.getLogger(BrooklynApiRestClientTest.class);

    private ManagementContext manager;
    private Server server;
    private BrooklynApi api;

    protected synchronized ManagementContext getManagementContext() throws Exception {
        if (manager == null) {
            manager = new LocalManagementContext();
            BrooklynRestApiLauncherTest.forceUseOfDefaultCatalogWithJavaClassPath(manager);
            BasicLocationRegistry.addNamedLocationLocalhost(manager);
            BrooklynRestApiLauncherTest.enableAnyoneLogin(manager);
        }
        return manager;
    }

    @BeforeClass
    public void setUp() throws Exception {
        server = BrooklynRestApiLauncher.launcher()
                .managementContext(getManagementContext())
                .securityProvider(TestSecurityProvider.class)
                .start();

        api = BrooklynApi.newInstance("http://localhost:" + ((NetworkConnector)server.getConnectors()[0]).getPort() + "/v1",
                TestSecurityProvider.USER, TestSecurityProvider.PASSWORD);
    }

    @AfterClass(alwaysRun = true)
    public void tearDown() throws Exception {
        for (Application app : getManagementContext().getApplications()) {
            try {
                ((StartableApplication) app).stop();
            } catch (Exception e) {
                log.warn("Error stopping app " + app + " during test teardown: " + e);
            }
        }
        Entities.destroyAll(getManagementContext());
        server.stop();
    }

    public void testNoV1InUrl() {
        api = BrooklynApi.newInstance("http://localhost:" + ((NetworkConnector)server.getConnectors()[0]).getPort(),
                TestSecurityProvider.USER, TestSecurityProvider.PASSWORD);

        assertEquals(api.getServerApi().getVersion().getVersion(), BrooklynVersion.get());
    }

    public void testLocationApi() throws Exception {
        log.info("Testing location API");
        Map<String, Map<String, Object>> locations = api.getLocationApi().getLocatedLocations();
        log.info("locations located are: "+locations);
    }

    public void testCatalogApiLocations() throws Exception {
        List<CatalogLocationSummary> locations = api.getCatalogApi().listLocations(".*", null, false);
        log.info("locations from catalog are: "+locations);
    }

    public void testCatalogCreate()throws Exception {
        final Response response = api.getCatalogApi().createFromUpload(getFileContentsAsString("catalog/test-catalog.bom").getBytes(), false);
        Asserts.assertEquals(response.getStatus(), 201);
        Asserts.assertStringContains(String.valueOf(response.getEntity()), "simple-tomcat:1.0");
    }



    public void testApplicationApiList() throws Exception {
        List<ApplicationSummary> apps = api.getApplicationApi().list(null);
        log.info("apps are: "+apps);
    }

    public void testApplicationApiCreate() throws Exception {
        Response r1 = api.getApplicationApi().createFromYaml("name: test-1234\n"
            + "services: [ { type: "+TestEntity.class.getName()+" } ]");
        HttpAsserts.assertHealthyStatusCode(r1.getStatus());
        log.info("creation result: "+r1.getEntity());
        List<ApplicationSummary> apps = api.getApplicationApi().list(null);
        log.info("apps with test: "+apps);
        Asserts.assertStringContains(apps.toString(), "test-1234");
    }

    public void testApplicationApiHandledError() throws Exception {
        Response r1 = api.getApplicationApi().createFromYaml("name: test");
        HttpAsserts.assertNotHealthyStatusCode(r1.getStatus());
        // new-style messages first, old-style messages after (during switch to TypePlanTransformer)
        Asserts.assertStringContainsAtLeastOne(r1.getEntity().toString().toLowerCase(),
            "invalid plan", "no services");
        Asserts.assertStringContainsAtLeastOne(r1.getEntity().toString().toLowerCase(),
            "format could not be recognized", "Unrecognized application blueprint format");
    }

    public void testApplicationApiThrownError() throws Exception {
        try {
            ApplicationSummary summary = api.getApplicationApi().get("test-5678");
            Asserts.shouldHaveFailedPreviously("got "+summary);
        } catch (Exception e) {
            Asserts.expectedFailureContainsIgnoreCase(e, "404", "not found");
        }
    }

    private String getFileContentsAsString(final String filename) throws Exception {
        final URL resource = getClass().getClassLoader().getResource(filename);
        Asserts.assertNotNull(resource);
        return new String(Files.readAllBytes(Paths.get(resource.toURI())), Charset.defaultCharset());
    }
}
