/*
 * 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.location.localhost;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;

import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.api.location.NoMachinesAvailableException;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.internal.BrooklynProperties;
import org.apache.brooklyn.core.location.BasicLocationRegistry;
import org.apache.brooklyn.core.location.internal.LocationInternal;
import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
import org.apache.brooklyn.location.byon.FixedListMachineProvisioningLocation;
import org.apache.brooklyn.location.localhost.LocalhostMachineProvisioningLocation;
import org.apache.brooklyn.location.ssh.SshMachineLocation;
import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import com.google.common.collect.ImmutableList;

public class LocalhostLocationResolverTest {

    private BrooklynProperties brooklynProperties;
    private LocalManagementContext managementContext;

    @BeforeMethod(alwaysRun=true)
    public void setUp() throws Exception {
        managementContext = LocalManagementContextForTests.newInstance();
        brooklynProperties = managementContext.getBrooklynProperties();
    }
    
    @AfterMethod(alwaysRun=true)
    public void tearDown() throws Exception {
        if (managementContext != null) Entities.destroyAll(managementContext);
    }
    
    @Test
    public void testTakesLocalhostScopedProperties() {
        brooklynProperties.put("brooklyn.location.localhost.privateKeyFile", "myprivatekeyfile");
        brooklynProperties.put("brooklyn.location.localhost.publicKeyFile", "mypublickeyfile");
        brooklynProperties.put("brooklyn.location.localhost.privateKeyData", "myprivateKeyData");
        brooklynProperties.put("brooklyn.location.localhost.publicKeyData", "myPublicKeyData");
        brooklynProperties.put("brooklyn.location.localhost.privateKeyPassphrase", "myprivateKeyPassphrase");

        Map<String, Object> conf = resolve("localhost").config().getBag().getAllConfig();
        
        assertEquals(conf.get("privateKeyFile"), "myprivatekeyfile");
        assertEquals(conf.get("publicKeyFile"), "mypublickeyfile");
        assertEquals(conf.get("privateKeyData"), "myprivateKeyData");
        assertEquals(conf.get("publicKeyData"), "myPublicKeyData");
        assertEquals(conf.get("privateKeyPassphrase"), "myprivateKeyPassphrase");
    }

    @Test
    public void testTakesLocalhostDeprecatedScopedProperties() {
        brooklynProperties.put("brooklyn.localhost.privateKeyFile", "myprivatekeyfile");
        brooklynProperties.put("brooklyn.localhost.publicKeyFile", "mypublickeyfile");
        brooklynProperties.put("brooklyn.localhost.privateKeyData", "myprivateKeyData");
        brooklynProperties.put("brooklyn.localhost.publicKeyData", "myPublicKeyData");
        brooklynProperties.put("brooklyn.localhost.privateKeyPassphrase", "myprivateKeyPassphrase");

        Map<String, Object> conf = resolve("localhost").config().getBag().getAllConfig();
        
        assertEquals(conf.get("privateKeyFile"), "myprivatekeyfile");
        assertEquals(conf.get("publicKeyFile"), "mypublickeyfile");
        assertEquals(conf.get("privateKeyData"), "myprivateKeyData");
        assertEquals(conf.get("publicKeyData"), "myPublicKeyData");
        assertEquals(conf.get("privateKeyPassphrase"), "myprivateKeyPassphrase");
    }

    @Test
    public void testTakesDeprecatedProperties() {
        brooklynProperties.put("brooklyn.localhost.private-key-file", "myprivatekeyfile");
        brooklynProperties.put("brooklyn.localhost.public-key-file", "mypublickeyfile");
        brooklynProperties.put("brooklyn.localhost.private-key-data", "myprivateKeyData");
        brooklynProperties.put("brooklyn.localhost.public-key-data", "myPublicKeyData");
        brooklynProperties.put("brooklyn.localhost.private-key-passphrase", "myprivateKeyPassphrase");
        Map<String, Object> conf = resolve("localhost").config().getBag().getAllConfig();
        
        assertEquals(conf.get("privateKeyFile"), "myprivatekeyfile");
        assertEquals(conf.get("publicKeyFile"), "mypublickeyfile");
        assertEquals(conf.get("privateKeyData"), "myprivateKeyData");
        assertEquals(conf.get("publicKeyData"), "myPublicKeyData");
        assertEquals(conf.get("privateKeyPassphrase"), "myprivateKeyPassphrase");
    }
    
    @Test
    public void testPropertyScopePrescedence() {
        brooklynProperties.put("brooklyn.location.named.mynamed", "localhost");
        
        // prefer those in "named" over everything else
        brooklynProperties.put("brooklyn.location.named.mynamed.privateKeyFile", "privateKeyFile-inNamed");
        brooklynProperties.put("brooklyn.location.localhost.privateKeyFile", "privateKeyFile-inProviderSpecific");
        brooklynProperties.put("brooklyn.localhost.privateKeyFile", "privateKeyFile-inGeneric");

        // prefer those in provider-specific over generic
        brooklynProperties.put("brooklyn.location.localhost.publicKeyFile", "publicKeyFile-inProviderSpecific");
        brooklynProperties.put("brooklyn.location.publicKeyFile", "publicKeyFile-inGeneric");

        // prefer location-generic if nothing else
        brooklynProperties.put("brooklyn.location.privateKeyData", "privateKeyData-inGeneric");

        Map<String, Object> conf = resolve("named:mynamed").config().getBag().getAllConfig();
        
        assertEquals(conf.get("privateKeyFile"), "privateKeyFile-inNamed");
        assertEquals(conf.get("publicKeyFile"), "publicKeyFile-inProviderSpecific");
        assertEquals(conf.get("privateKeyData"), "privateKeyData-inGeneric");
    }

    @Test
    public void testLocalhostLoads() {
        Assert.assertTrue(resolve("localhost") instanceof LocalhostMachineProvisioningLocation);
    }

    @Test
    public void testThrowsOnInvalid() throws Exception {
        assertThrowsNoSuchElement("wrongprefix");
        assertThrowsIllegalArgument("localhost(name=abc"); // no closing bracket
        assertThrowsIllegalArgument("localhost(name)"); // no value for name
        assertThrowsIllegalArgument("localhost(name=)"); // no value for name
    }
    

    @Test
    public void testAcceptsList() {
        List<Location> l = getLocationResolver().getListOfLocationsManaged(ImmutableList.of("localhost"));
        assertEquals(l.size(), 1, "l="+l);
        assertTrue(l.get(0) instanceof LocalhostMachineProvisioningLocation, "l="+l);
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testRegistryCommaResolution() throws NoMachinesAvailableException {
        List<Location> l;
        l = getLocationResolver().getListOfLocationsManaged(JavaStringEscapes.unwrapJsonishListIfPossible("localhost,localhost,localhost"));
        assertEquals(l.size(), 3, "l="+l);
        assertTrue(l.get(0) instanceof LocalhostMachineProvisioningLocation, "l="+l);
        assertTrue(l.get(1) instanceof LocalhostMachineProvisioningLocation, "l="+l);
        assertTrue(l.get(2) instanceof LocalhostMachineProvisioningLocation, "l="+l);

        // And check works if comma in brackets
        l = getLocationResolver().getListOfLocationsManaged(JavaStringEscapes.unwrapJsonishListIfPossible(
            "[ \"byon:(hosts=\\\"192.168.0.1\\\",user=bob)\", \"byon:(hosts=\\\"192.168.0.2\\\",user=bob2)\" ]"));
        assertEquals(l.size(), 2, "l="+l);
        assertTrue(l.get(0) instanceof FixedListMachineProvisioningLocation, "l="+l);
        assertTrue(l.get(1) instanceof FixedListMachineProvisioningLocation, "l="+l);
        assertEquals(((FixedListMachineProvisioningLocation<SshMachineLocation>)l.get(0)).obtain().getUser(), "bob");
        assertEquals(((FixedListMachineProvisioningLocation<SshMachineLocation>)l.get(1)).obtain().getUser(), "bob2");
    }

    @Test(expectedExceptions={NoSuchElementException.class})
    public void testRegistryCommaResolutionInListNotAllowed1() throws NoMachinesAvailableException {
        // disallowed since 0.7.0
        getLocationResolver().getListOfLocationsManaged(ImmutableList.of("localhost,localhost,localhost"));
    }

    @Test(expectedExceptions={IllegalArgumentException.class})
    public void testRegistryCommaResolutionInListNotAllowed2() throws NoMachinesAvailableException {
        // disallowed since 0.7.0
        // fails because it interprets the entire string as a single spec, which does not parse
        getLocationResolver().getListOfLocationsManaged(ImmutableList.of("localhost(),localhost()"));
    }

    @Test(expectedExceptions={IllegalArgumentException.class})
    public void testRegistryCommaResolutionInListNotAllowed3() throws NoMachinesAvailableException {
        // disallowed since 0.7.0
        // fails because it interprets the entire string as a single spec, which does not parse
        getLocationResolver().getListOfLocationsManaged(ImmutableList.of("localhost(name=a),localhost(name=b)"));
    }

    @Test(expectedExceptions={IllegalArgumentException.class})
    public void testDoesNotAcceptsListOLists() {
        ((BasicLocationRegistry)managementContext.getLocationRegistry()).getListOfLocationsManaged(ImmutableList.of(ImmutableList.of("localhost")));
    }

    @Test
    public void testResolvesExplicitName() throws Exception {
        Location location = resolve("localhost(name=myname)");
        assertTrue(location instanceof LocalhostMachineProvisioningLocation);
        assertEquals(location.getDisplayName(), "myname");
    }
    
    @Test
    public void testWithOldStyleColon() throws Exception {
        Location location = resolve("localhost:(name=myname)");
        assertTrue(location instanceof LocalhostMachineProvisioningLocation);
        assertEquals(location.getDisplayName(), "myname");
    }
    
    @Test
    public void testResolvesPropertiesInSpec() throws Exception {
        LocationInternal location = resolve("localhost(privateKeyFile=myprivatekeyfile,name=myname)");
        assertTrue(location instanceof LocalhostMachineProvisioningLocation);
        assertEquals(location.getDisplayName(), "myname");
        assertEquals(location.config().getBag().getStringKey("privateKeyFile"), "myprivatekeyfile");
    }
    
    @Test
    public void testResolvesDefaultName() throws Exception {
        Location location = resolve("localhost");
        assertTrue(location instanceof LocalhostMachineProvisioningLocation);
        assertEquals(location.getDisplayName(), "localhost");

        Location location2 = resolve("localhost()");
        assertTrue(location2 instanceof LocalhostMachineProvisioningLocation);
        assertEquals(location2.getDisplayName(), "localhost");
    }
    
    private BasicLocationRegistry getLocationResolver() {
        return (BasicLocationRegistry) managementContext.getLocationRegistry();
    }
    
    private LocationInternal resolve(String val) {
        Location l = managementContext.getLocationRegistry().getLocationManaged(val);
        Assert.assertNotNull(l);
        return (LocationInternal) l;
    }
    
    private void assertThrowsNoSuchElement(String val) {
        try {
            resolve(val);
            fail();
        } catch (NoSuchElementException e) {
            // success
        }

        // and check the long form returns an Absent (not throwing)
        Assert.assertTrue(managementContext.getLocationRegistry().getLocationSpec(val).isAbsent());
    }
    
    private void assertThrowsIllegalArgument(String val) {
        try {
            resolve(val);
            fail();
        } catch (IllegalArgumentException e) {
            // success
        }
        
        // and check the long form returns an Absent (not throwing)
        Assert.assertTrue(managementContext.getLocationRegistry().getLocationSpec(val).isAbsent());
    }
}
