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

import java.io.ByteArrayOutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.apache.brooklyn.api.location.NoMachinesAvailableException;
import org.apache.brooklyn.location.ssh.SshMachineLocation;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.ssh.BashCommands;
import org.apache.brooklyn.util.text.Identifiers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;

public class SimpleJcloudsLocationUserLoginAndConfigLiveTest extends AbstractJcloudsLiveTest {

    // FIXME And tidy up this one
    
    private static final String LOCATION_SPEC = AWS_EC2_PROVIDER + ":" + AWS_EC2_USEAST_REGION_NAME;
    
    private static final Logger log = LoggerFactory.getLogger(SimpleJcloudsLocationUserLoginAndConfigLiveTest.class);
    
    @BeforeMethod(alwaysRun=true)
    @Override
    public void setUp() throws Exception {
        super.setUp();
        jcloudsLocation = resolve(LOCATION_SPEC);
    }
    
    @SuppressWarnings("rawtypes")
    @Test(groups="Live")
    public void testJcloudsCreateBogStandard() throws Exception {
        log.info("TEST testJcloudsCreateBogStandard");
        JcloudsSshMachineLocation m1 = obtainMachine(ImmutableMap.of());

        Map details = MutableMap.of("id", m1.getJcloudsId(), "hostname", m1.getAddress().getHostAddress(), "user", m1.getUser());
        log.info("got machine "+m1+" at "+jcloudsLocation+": "+details+"; now trying to rebind");
        String result;
        // echo conflates spaces of arguments
        result = execWithOutput(m1, Arrays.asList("echo trying  m1", "hostname", "date"));
        Assert.assertTrue(result.contains("trying m1"));
        
        log.info("now trying rebind "+m1);
        JcloudsSshMachineLocation m2 = jcloudsLocation.rebindMachine(details);
        result = execWithOutput(m2, Arrays.asList("echo trying  m2", "hostname", "date"));
        Assert.assertTrue(result.contains("trying m2"));
    }

    @SuppressWarnings("rawtypes")
    @Test(groups="Live")
    public void testJcloudsCreateBogStandardWithUserBrooklyn() throws Exception {
        log.info("TEST testJcloudsCreateBogStandardWithUserBrooklyn");
        JcloudsSshMachineLocation m1 = obtainMachine(MutableMap.of("user", "brooklyn"));

        Map details = MutableMap.of("id", m1.getJcloudsId(), "hostname", m1.getAddress().getHostAddress(), "user", m1.getUser());
        log.info("got machine "+m1+" at "+jcloudsLocation+": "+details+"; now trying to rebind");
        String result;
        // echo conflates spaces of arguments
        result = execWithOutput(m1, Arrays.asList("echo trying  m1", "hostname", "date"));
        Assert.assertTrue(result.contains("trying m1"));
        
        log.info("now trying rebind "+m1);
        JcloudsSshMachineLocation m2 = jcloudsLocation.rebindMachine(details);
        result = execWithOutput(m2, Arrays.asList("echo trying  m2", "hostname", "date"));
        Assert.assertTrue(result.contains("trying m2"));
        
        Assert.assertEquals(m2.getUser(), "brooklyn");
    }
    
    @SuppressWarnings("rawtypes")
    @Test(groups="Live")
    public void testJcloudsCreateUserMetadata() throws Exception {
        log.info("TEST testJcloudsCreateBogStandard");
        String key = "brooklyn-test-user-data";
        String value = "test-"+Identifiers.makeRandomId(4);
        JcloudsSshMachineLocation m1 = obtainMachine(MutableMap.of("userMetadata", key+"="+value));

        Map details = MutableMap.of("id", m1.getJcloudsId(), "hostname", m1.getAddress().getHostAddress(), "user", m1.getUser(),
                "userMetadata", key+"="+value);
        Assert.assertEquals(m1.node.getUserMetadata().get(key), value);
        
        log.info("got machine "+m1+" at "+jcloudsLocation+": "+details+"; now trying to rebind");
        String result;
        // echo conflates spaces of arguments
        result = execWithOutput(m1, Arrays.asList("echo trying  m1", "hostname", "date"));
        Assert.assertTrue(result.contains("trying m1"));
        
        log.info("now trying rebind "+m1);
        JcloudsSshMachineLocation m2 = jcloudsLocation.rebindMachine(details);
        result = execWithOutput(m2, Arrays.asList("echo trying  m2", "hostname", "date"));
        Assert.assertTrue(result.contains("trying m2"));
        Assert.assertEquals(m2.node.getUserMetadata().get(key), value);
    }

    // a curious image, centos, but user is ec2-user, and handily not correctly auto-detected
    // test we can specify a loginUser different from user, and that user is created etc...
    // imageId=us-east-1/ami-f95cf390
    public static final String EC2_CENTOS_IMAGE = "us-east-1/ami-f95cf390";
    
    @Test(groups="Live")
    public void testJcloudsMissingUser() throws Exception {
        log.info("TEST testJcloudsMissingUser");
        try {
            // wait up to 30s for login (override default of 5m so test runs faster)
            obtainMachine(MutableMap.of("imageId", EC2_CENTOS_IMAGE, "waitForSshable", 30*1000));
            log.info("whoops we logged in");
        } catch (NoMachinesAvailableException e) {
            log.info("got error as expected, for missing user: "+e); // success
        }
    }

    @SuppressWarnings("rawtypes")
    @Test(groups="Live")
    public void testJcloudsWithSpecificLoginUserAndSameUser() throws Exception {
        log.info("TEST testJcloudsWithSpecificLoginUserAndSameUser");
        JcloudsSshMachineLocation m1 = obtainMachine(MutableMap.of(
                "imageId", EC2_CENTOS_IMAGE,
                "loginUser", "ec2-user",
                "user", "ec2-user",
                "waitForSshable", 30*1000));

        Map details = MutableMap.of("id", m1.getJcloudsId(), "hostname", m1.getAddress().getHostAddress(), "user", m1.getUser());
        log.info("got machine "+m1+" at "+jcloudsLocation+": "+details+"; now trying to rebind");
        String result;
        // echo conflates spaces of arguments
        result = execWithOutput(m1, Arrays.asList("echo trying  m1", "hostname", "date"));
        Assert.assertTrue(result.contains("trying m1"));
        
        log.info("now trying rebind "+m1);
        JcloudsSshMachineLocation m2 = jcloudsLocation.rebindMachine(details);
        result = execWithOutput(m2, Arrays.asList("echo trying  m2", "hostname", "date"));
        Assert.assertTrue(result.contains("trying m2"));
        
        Assert.assertEquals(m2.getUser(), "ec2-user");
    }

    @SuppressWarnings("rawtypes")
    @Test(groups="Live")
    public void testJcloudsWithSpecificLoginUserAndNewUser() throws Exception {
        log.info("TEST testJcloudsWithSpecificLoginUserAndNewUser");
        JcloudsSshMachineLocation m1 = obtainMachine(MutableMap.of(
                "imageId", EC2_CENTOS_IMAGE,
                "loginUser", "ec2-user",
                "user", "newbob",
                "waitForSshable", 30*1000));

        Map details = MutableMap.of("id", m1.getJcloudsId(), "hostname", m1.getAddress().getHostAddress(), "user", m1.getUser());
        log.info("got machine "+m1+" at "+jcloudsLocation+": "+details+"; now trying to rebind");
        String result;
        // echo conflates spaces of arguments
        result = execWithOutput(m1, Arrays.asList("echo trying  m1", "hostname", "date"));
        Assert.assertTrue(result.contains("trying m1"));
        
        log.info("now trying rebind "+m1);
        JcloudsSshMachineLocation m2 = jcloudsLocation.rebindMachine(details);
        result = execWithOutput(m2, Arrays.asList("echo trying  m2", "hostname", "date"));
        Assert.assertTrue(result.contains("trying m2"));
        
        Assert.assertEquals(m2.getUser(), "newbob");
    }

    @SuppressWarnings("rawtypes")
    @Test(groups="Live")
    public void testJcloudsWithSpecificLoginUserAndDefaultUser() throws Exception {
        log.info("TEST testJcloudsWithSpecificLoginUserAndDefaultUser");
        JcloudsSshMachineLocation m1 = obtainMachine(MutableMap.of(
                "imageId", EC2_CENTOS_IMAGE,
                "loginUser", "ec2-user",
                "waitForSshable", 30*1000));

        Map details = MutableMap.of("id", m1.getJcloudsId(), "hostname", m1.getAddress().getHostAddress(), "user", m1.getUser());
        log.info("got machine "+m1+" at "+jcloudsLocation+": "+details+"; now trying to rebind");
        String result;
        // echo conflates spaces of arguments
        result = execWithOutput(m1, Arrays.asList("echo trying  m1", "hostname", "date"));
        Assert.assertTrue(result.contains("trying m1"));
        
        log.info("now trying rebind "+m1);
        JcloudsSshMachineLocation m2 = jcloudsLocation.rebindMachine(details);
        result = execWithOutput(m2, Arrays.asList("echo trying  m2", "hostname", "date"));
        Assert.assertTrue(result.contains("trying m2"));
    }

    @Test(groups="Live")
    public void testJcloudsCreateWithNoSudoGranted() throws Exception {
        log.info("TEST testJcloudsCreateWithNoSudoGranted");
        JcloudsSshMachineLocation m = obtainMachine(MutableMap.of(
                "grantUserSudo", false,
                "waitForSshable", 30*1000));

        int exitCode = execWithExitCode(m, ImmutableList.of(BashCommands.sudo("echo yes")));
        Assert.assertFalse(exitCode == 0, "exit code for sudo command should not have been 0");
    }

    private String execWithOutput(SshMachineLocation m, List<String> commands) {
        ByteArrayOutputStream stdout = new ByteArrayOutputStream();
        ByteArrayOutputStream stderr = new ByteArrayOutputStream();
        exec(m, commands, stdout, stderr);
        return new String(stdout.toByteArray());
    }

    private int execWithExitCode(SshMachineLocation m, List<String> commands) {
        ByteArrayOutputStream stdout = new ByteArrayOutputStream();
        ByteArrayOutputStream stderr = new ByteArrayOutputStream();
        return exec(m, commands, stdout, stderr);
    }

    private int exec(SshMachineLocation m, List<String> commands, ByteArrayOutputStream stdout, ByteArrayOutputStream stderr) {
        Map<String, Object> flags = Maps.newLinkedHashMap();
        flags.put("out", stdout);
        flags.put("err", stderr);
        int exitCode = m.execCommands(flags, "test", commands);
        log.info("stdout from "+commands+":\n"+new String(stdout.toByteArray()));
        log.info("stderr from "+commands+":\n"+new String(stderr.toByteArray()));
        log.info("exit code: " + exitCode);
        return exitCode;
    }

    private JcloudsLocation resolve(String spec) {
        return (JcloudsLocation) managementContext.getLocationRegistry().getLocationManaged("jclouds:"+spec);
    }
}
