/*******************************************************************************
 * Copyright (c) 2015 Red Hat, Inc. Distributed under license by Red Hat, Inc.
 * All rights reserved. This program is made available under the terms of the
 * Eclipse Public License v1.0 which accompanies this distribution, and is
 * available at http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors: Red Hat, Inc.
 ******************************************************************************/
package com.openshift.internal.restclient.model.v1;

import static com.openshift.internal.util.JBossDmrExtentions.getPath;
import static org.fest.assertions.Assertions.assertThat;
import static org.fest.assertions.MapAssert.*;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import org.jboss.dmr.ModelNode;
import org.json.JSONException;
import org.junit.Before;
import org.junit.Test;
import org.skyscreamer.jsonassert.JSONAssert;

import com.openshift.internal.restclient.model.ModelNodeBuilder;
import com.openshift.internal.restclient.model.ReplicationController;
import com.openshift.internal.restclient.model.properties.ResourcePropertiesRegistry;
import com.openshift.internal.restclient.model.volume.EmptyDirVolumeSource;
import com.openshift.internal.restclient.model.volume.SecretVolumeSource;
import com.openshift.restclient.IClient;
import com.openshift.restclient.ResourceKind;
import com.openshift.restclient.images.DockerImageURI;
import com.openshift.restclient.model.IConfigMapKeySelector;
import com.openshift.restclient.model.IContainer;
import com.openshift.restclient.model.IEnvironmentVariable;
import com.openshift.restclient.model.IEnvironmentVariable.IEnvVarSource;
import com.openshift.restclient.model.IObjectFieldSelector;
import com.openshift.restclient.model.IPort;
import com.openshift.restclient.model.IReplicationController;
import com.openshift.restclient.model.ISecretKeySelector;
import com.openshift.restclient.model.volume.IVolume;
import com.openshift.restclient.model.volume.IVolumeMount;
import com.openshift.restclient.model.volume.IVolumeSource;
import com.openshift.restclient.utils.Samples;

/**
 * @author Jeff Cantrill
 */
public class ReplicationControllerTest {

	private static final String VERSION = "v1";
	private static IReplicationController rc;
	private static ModelNode node;
	private static IClient client;
	
	@Before
	public void setup(){
		client = mock(IClient.class);
		node = ModelNode.fromJSONString(Samples.V1_REPLICATION_CONTROLLER.getContentAsString());
		rc = new ReplicationController(node, client, ResourcePropertiesRegistry.getInstance().get(VERSION, ResourceKind.REPLICATION_CONTROLLER));
	}
	
	public void testGetEnvironmentVariablesWithValueFrom() {

		Collection<IEnvironmentVariable> envVars = rc.getEnvironmentVariables();
		
		//fieldref
		Optional<IEnvironmentVariable> envVar = envVars.stream().filter(e->"OPENSHIFT_KUBE_PING_NAMESPACE".equals(e.getName())).findFirst();
		assertTrue("Exp. to find env var", envVar.isPresent());
		IEnvVarSource from = envVar.get().getValueFrom();
		assertTrue(from instanceof IObjectFieldSelector);
		IObjectFieldSelector selector = (IObjectFieldSelector)from;
		assertEquals("v1",selector.getApiVersion());
		assertEquals("metadata.namespace",selector.getFieldPath());

		//configmapkeyref
		envVar = envVars.stream().filter(e->"OPENSHIFT_CONFIGMAP_KEY_REF".equals(e.getName())).findFirst();
		assertTrue("Exp. to find env var", envVar.isPresent());
		from = envVar.get().getValueFrom();
		assertTrue(from instanceof IConfigMapKeySelector);
		IConfigMapKeySelector configSelector = (IConfigMapKeySelector) from;
		assertEquals("xyz",configSelector.getName());
		assertEquals("abc123",configSelector.getKey());

		//secretkeyref
		envVar = envVars.stream().filter(e->"OPENSHIFT_SECRET_KEY_REF".equals(e.getName())).findFirst();
		assertTrue("Exp. to find env var", envVar.isPresent());
		from = envVar.get().getValueFrom();
		assertTrue(from instanceof ISecretKeySelector);
		ISecretKeySelector secretKeySelector = (ISecretKeySelector) from;
		assertEquals("bar",secretKeySelector.getName());
		assertEquals("foo",secretKeySelector.getKey());
	}
	
	@Test
	public void testEnvironmentVariable() {
		//add
		rc.setEnvironmentVariable("foo", "bar");
		Collection<IEnvironmentVariable> envVars = rc.getEnvironmentVariables();
		Optional<IEnvironmentVariable> envVar = envVars.stream().filter(e->"foo".equals(e.getName())).findFirst();
		assertTrue("Exp. to find env var", envVar.isPresent());
		assertEquals("bar", envVar.get().getValue());

		//update
		int size = rc.getEnvironmentVariables().size();
		rc.setEnvironmentVariable("foo", "baz");
		assertEquals(size, rc.getEnvironmentVariables().size());
		envVars = rc.getEnvironmentVariables();
		envVar = envVars.stream().filter(e->"foo".equals(e.getName())).findFirst();
		assertEquals("baz", envVar.get().getValue());

		rc.removeEnvironmentVariable("foo");
		assertEquals(size - 1, rc.getEnvironmentVariables().size());
	}

	@Test
	public void testEnvironmentVariableFromMissingEnv() {
		String[] path = getPath(ReplicationController.SPEC_TEMPLATE_CONTAINERS);
		ModelNode containers = node.get(path);
		containers.get(0).remove("env");
		testEnvironmentVariable();
	}

	@Test
	public void testEnvironmentVariableForANamedContainer() {
		rc.setEnvironmentVariable("ruby-helloworld-database", "fooz", "balls");
		Collection<IEnvironmentVariable> envVars = rc.getEnvironmentVariables("ruby-helloworld-database");
		Optional<IEnvironmentVariable> envVar = envVars.stream().filter(e->"fooz".equals(e.getName())).findFirst();
		assertTrue("Exp. to find env var", envVar.isPresent());
		assertEquals("balls", envVar.get().getValue());
	}
	
	@Test
	public void setReplicaSelector() {
		Map<String, String> exp = new HashMap<>();
		exp.put("foo", "bar");
		rc.setReplicaSelector(exp);
		assertEquals(exp, rc.getReplicaSelector());
	}

	@Test
	public void setReplicaSelectorFromMissingSelector() {
		String[] path = new String[]{"status"};
		node.get(path).clear();
		setReplicaSelector();
	}


	@Test
	public void setReplicaSelectorWithKeyValue() {
		Map<String, String> exp = new HashMap<>();
		exp.put("foo", "bar");
		rc.setReplicaSelector("foo","bar");
		assertEquals(exp, rc.getReplicaSelector());
	}
	
	@Test
	public void getReplicaSelector() {
		Map<String, String> labels = new HashMap<>();
		labels.put("name", "database");
		labels.put("deployment", "database-1");
		labels.put("deploymentconfig", "database");
		assertEquals(labels, rc.getReplicaSelector());
	}
	
	@Test
    public void getServiceAccountName() {
        assertEquals("dbServiceAccountName", rc.getServiceAccountName());
    }
	
	@Test
    public void setServiceAccountName() {
	    rc.setServiceAccountName("newDBServiceAccountName");
        assertEquals("newDBServiceAccountName", rc.getServiceAccountName());
    }
	
	@Test
	public void getDesiredReplicaCount(){
		assertEquals(1, rc.getDesiredReplicaCount());
	}

	@Test
	public void setDesiredReplicaCount(){
		rc.setDesiredReplicaCount(9);
		assertEquals(9, rc.getDesiredReplicaCount());

		rc.setReplicas(6);
		assertEquals(6, rc.getDesiredReplicaCount());
	}
	
	@Test
	public void getCurrentReplicaCount(){
		assertEquals(2, rc.getCurrentReplicaCount());
	}
	
	@Test
	public void testGetImages(){
		String [] exp = new String []{"openshift/mysql-55-centos7:latest"};
		assertArrayEquals(exp , rc.getImages().toArray());
	}
	
	@Test 
	public void testGetContainer() {
		assertNull(rc.getContainer(" "));
		assertNotNull(rc.getContainer("ruby-helloworld-database"));
	}
	@Test 
	public void testGetContainers() {
		Collection<IContainer> containers = rc.getContainers();
		assertNotNull(containers);
		assertEquals(1, containers.size());
		
		String[] path = getPath(ReplicationController.SPEC_TEMPLATE_CONTAINERS);
		node.get(path).clear();
		assertNotNull(rc.getContainers());
	}
	
	@Test
	public void testAddContainer() throws JSONException {
		//remove containers hack
		String[] path = getPath(ReplicationController.SPEC_TEMPLATE_CONTAINERS);
		node.get(path).clear();
		//setup
		DockerImageURI uri = new DockerImageURI("aproject/an_image_name");
		IPort port = mock(IPort.class);
		when(port.getProtocol()).thenReturn("TCP");
		when(port.getContainerPort()).thenReturn(8080);
		Set<IPort> ports = new HashSet<>();
		ports.add(port);
		
		IContainer container = rc.addContainer(uri.getName(), uri, ports, new HashMap<String, String>(), Arrays.asList("/tmp"));
		
		List<ModelNode> containers = node.get(path).asList();
		assertEquals(1, containers.size());
		
		ModelNode exp = new ModelNodeBuilder()
			.set("name", uri.getName())
			.set("image",uri.toString())
			.add("ports", new ModelNodeBuilder()
				.set("containerPort", port.getContainerPort())
				.set("protocol", port.getProtocol())
			)
			.add("volumeMounts", new ModelNodeBuilder()
				.set("name", uri.getName() +"-"+1)
				.set("mountPath", "/tmp")
				.set("readOnly", false)
			)
			.build();
		
		JSONAssert.assertEquals(exp.toJSONString(false), container.toJSONString(), true);
		
		Object [] sourceNames = rc.getVolumes().stream().map(IVolumeSource::getName).toArray();

		Object [] contVolNames = container.getVolumes().stream().map(IVolume::getName).toArray();
		assertArrayEquals(sourceNames,contVolNames);
	}
	
	@Test
	public void testAddContainerAllowsContainerToBeFurtherManipulated()  throws JSONException{
		//remove containers hack
		String[] path = getPath(ReplicationController.SPEC_TEMPLATE_CONTAINERS);
		node.get(path).clear();
		
		//setup
		DockerImageURI uri = new DockerImageURI("arepo/aproject/an_image_name");
		IPort port = mock(IPort.class);
		when(port.getProtocol()).thenReturn("TCP");
		when(port.getContainerPort()).thenReturn(8080);
		Set<IPort> ports = new HashSet<>();
		ports.add(port);
		
		IVolumeMount mount = mock(IVolumeMount.class);
		when(mount.getName()).thenReturn(uri.getName() +"-"+1);
		when(mount.getMountPath()).thenReturn("/tmp");
		when(mount.isReadOnly()).thenReturn(Boolean.FALSE);
		Set<IVolumeMount> mounts = new HashSet<>();
		mounts.add(mount);
		
		IContainer container = rc.addContainer(uri.getName());
		container.setImage(uri);
		container.setPorts(ports);
		container.setVolumeMounts(mounts);
		
		IVolumeMount fooVolumeMount = container.addVolumeMount("foobar");
		fooVolumeMount.setMountPath("/tmp2");
		
		ModelNode exp = new ModelNodeBuilder()
				.set("name", uri.getName())
				.set("image",uri.toString())
				.add("ports", new ModelNodeBuilder()
					.set("containerPort", port.getContainerPort())
					.set("protocol", port.getProtocol())
				)
				.add("volumeMounts", new ModelNodeBuilder()
					.set("name", uri.getName() +"-"+1)
					.set("mountPath", "/tmp")
					.set("readOnly", false)
				)
				.add("volumeMounts", new ModelNodeBuilder()
						.set("name", "foobar")
						.set("mountPath", "/tmp2")
						)
				.build();
		
		JSONAssert.assertEquals(exp.toJSONString(false), container.toJSONString(), true);
	}
	
	@Test
	public void shouldReturnTemplateLabels() {
		Map<String, String> labels = rc.getTemplateLabels();
		assertThat(labels)
				.hasSize(3)
				.includes(entry("deployment", "database-1"))
				.includes(entry("deploymentconfig", "database"))
				.includes(entry("name", "database"));
	}

	@Test
	public void testSetVolumes() {
		IVolumeSource source = new EmptyDirVolumeSource("myvolume");
		rc.setVolumes(Collections.singleton(source));
		Set<IVolumeSource> volumes = rc.getVolumes();
		assertEquals(1, volumes.size());
		assertEquals("myvolume", volumes.iterator().next().getName());
	}

	@Test
	public void testAddSecretVolumeToPodSpec() throws JSONException {
            IVolumeMount volumeMount = new IVolumeMount() {
                    public String getName() { return "my-secret"; }
                    public String getMountPath() { return "/path/to/my/secret/"; }
                    public boolean isReadOnly() { return true; }
                    public void setName(String name) {}
                    public void setMountPath(String path) {}
                    public void setReadOnly(boolean readonly) {}
                };
            SecretVolumeSource source = new SecretVolumeSource(volumeMount.getName());
		    source.setSecretName("the-secret");
	        rc.addVolume(source);
            Set<IVolumeSource> podSpecVolumes = rc.getVolumes();
            Optional vol = podSpecVolumes.stream()
                .filter(v->v.getName().equals(volumeMount.getName()))
                .findFirst();
            assertTrue("Expected to find secret volume in pod spec", vol.isPresent());
        }
}
