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

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Properties;
import java.util.Random;

import org.jboss.dmr.ModelNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.openshift.internal.restclient.model.DeploymentConfig;
import com.openshift.internal.restclient.model.ModelNodeBuilder;
import com.openshift.internal.restclient.model.Pod;
import com.openshift.internal.restclient.model.ReplicationController;
import com.openshift.internal.restclient.model.properties.ResourcePropertyKeys;
import com.openshift.restclient.ClientBuilder;
import com.openshift.restclient.IClient;
import com.openshift.restclient.NotFoundException;
import com.openshift.restclient.ResourceKind;
import com.openshift.restclient.images.DockerImageURI;
import com.openshift.restclient.model.IDeploymentConfig;
import com.openshift.restclient.model.IPod;
import com.openshift.restclient.model.IProject;
import com.openshift.restclient.model.IReplicationController;
import com.openshift.restclient.model.IResource;
import com.openshift.restclient.model.deploy.DeploymentTriggerType;

import static org.junit.Assert.fail;

/**
 * @author Jeff Cantrill
 */
public class IntegrationTestHelper implements ResourcePropertyKeys {

	public static final long MILLISECONDS_PER_SECOND = 1000;
	public static final long MILLISECONDS_PER_MIN = MILLISECONDS_PER_SECOND * 60;
	
	private static final String KEY_DEFAULT_PROJECT = "default.project";
	private static final String KEY_SERVER_URL = "serverURL";
	private static final String KEY_PASSWORD = "default.clusteradmin.password";
	private static final String KEY_USER = "default.clusteradmin.user";
	private static final String KEY_OPENSHIFT_LOCATION = "default.openshift.location";

	private static final String INTEGRATIONTEST_PROPERTIES = "/openshiftv3IntegrationTest.properties";

	private static final Logger LOG = LoggerFactory.getLogger(IntegrationTestHelper.class);

	private final Properties prop;

	public IntegrationTestHelper() {
		this.prop = loadProperties(INTEGRATIONTEST_PROPERTIES);
	}

	public IClient createClient(){
		return new ClientBuilder(prop.getProperty(KEY_SERVER_URL)).build();
	}

	public IClient createClientForBasicAuth() {
		IClient client = new ClientBuilder(prop.getProperty(KEY_SERVER_URL))
				.withUserName(getDefaultClusterAdminUser())
				.withPassword(getDefaultClusterAdminPassword())
				.build();
		return client;
	}

	public String getDefaultNamespace(){
		return prop.getProperty(KEY_DEFAULT_PROJECT);
	}

	public String generateNamespace() {
		return String.format("%s-%s",getDefaultNamespace(), new Random().nextInt(9999));
	}
	
	public IProject generateProject(IClient client) {
		IResource request = client.getResourceFactory().stub(ResourceKind.PROJECT_REQUEST, generateNamespace());
		return (IProject) client.create(request);
	}

	/**
	 * Stub a pod definition to the openshift/hello-openshift
	 * image for purposes of testing.
	 * @param client
	 * @param project
	 * @return a pod definition that needs to be further created using the client
	 */
	public static IPod stubPod(IClient client, IProject project) {
		//cluster shouldnt allow us to create pods directly
		ModelNode builder = new ModelNodeBuilder()
				.set(ResourcePropertyKeys.KIND, ResourceKind.POD)
				.set(ResourcePropertyKeys.METADATA_NAME, "hello-openshift")
				.set(ResourcePropertyKeys.METADATA_NAMESPACE, project.getName())
				.add("spec.containers", new ModelNodeBuilder()
					.set(ResourcePropertyKeys.NAME, "hello-openshift")
					.set("image", "openshift/hello-openshift")
					.add("ports", new ModelNodeBuilder()
						.set("containerPort", 8080)
						.set("protocol", "TCP")
					)
				)
				.build();
		return new Pod(builder, client, new HashMap<>());
	}
	
	public static IDeploymentConfig stubDeploymentConfig(IClient client, IProject project) {
		IDeploymentConfig dc = new ResourceFactory(client).create("v1", ResourceKind.DEPLOYMENT_CONFIG);
		((DeploymentConfig)dc).setName("hello-openshift");
		((DeploymentConfig)dc).setNamespace(project.getName());
		dc.setReplicas(1);
		dc.setReplicaSelector("foo","bar");
		dc.addContainer(dc.getName(), new DockerImageURI("openshift/hello-openshift"), new HashSet<>(), Collections.emptyMap(), Collections.emptyList());
		dc.addTrigger(DeploymentTriggerType.CONFIG_CHANGE);
		return dc;
	}
	
	public static IReplicationController stubReplicationController(IClient client, IProject project) {
		IReplicationController rc = new ResourceFactory(client).create("v1", ResourceKind.REPLICATION_CONTROLLER);
		((ReplicationController)rc).setName("hello-openshift-rc");
		((ReplicationController)rc).setNamespace(project.getName());
		rc.setReplicas(1);
		rc.setReplicaSelector("foo","bar");
		rc.addContainer(rc.getName(), new DockerImageURI("openshift/hello-openshift"), new HashSet<>(), Collections.emptyMap(), Collections.emptyList());
		return rc;
	}

	/**
	 * Loads the properties from the given {@code propertyFileName}, then
	 * overrides from the System properties if any was given (this is a convenient way to
	 * override the default settings and avoid conflicting with the properties file in git)
	 * 
	 * @return the properties to use in the test
	 * @throws IOException 
	 */
	private static Properties loadProperties(final String propertyFileName) {
		final Properties properties = new Properties();
		try {
			properties.load(IntegrationTestHelper.class.getResourceAsStream(propertyFileName));
		} catch (IOException e) {
			e.printStackTrace();
			fail("Failed to load properties from file " + INTEGRATIONTEST_PROPERTIES + ": " + e.getMessage());
		}
		overrideIfExists(properties, KEY_SERVER_URL);
		overrideIfExists(properties, KEY_DEFAULT_PROJECT);
		overrideIfExists(properties, KEY_OPENSHIFT_LOCATION);
		overrideIfExists(properties, KEY_USER);
		overrideIfExists(properties, KEY_PASSWORD);
		return properties;
	}
	
	
	private static void overrideIfExists(final Properties properties, final String propertyName){
		// then override with the VM arguments (if any)
		final String propertyValue = System.getProperty(propertyName);
		if (propertyValue != null) {
			properties.setProperty(propertyName, propertyValue);
		}
	}

	public String getOpenShiftLocation() {
		return  prop.getProperty(KEY_OPENSHIFT_LOCATION);
	}

	public String getDefaultClusterAdminUser() {
		return  prop.getProperty(KEY_USER);
	}

	public String getDefaultClusterAdminPassword() {
		return  prop.getProperty(KEY_PASSWORD);
	}

	public String getServerUrl() {
		return  prop.getProperty(KEY_SERVER_URL);
	}
	
	public static void cleanUpResource(IClient client, IResource resource) {
		if(client == null || resource == null) {
			LOG.debug("Skipping cleanup as client %s or resource %s is null", client, resource);
		}
		try {
			Thread.sleep(1000);
			LOG.debug(String.format("Deleting resource: %s", resource));
			client.delete(resource);
		} catch (Exception e) {
			LOG.warn("Exception deleting", e);
		}
	}
	
	/**
	 * Wait for the resource to exist for cases where the test is faster
	 * then the server in reconciling its existence;
	 * @param client
	 * @param kind
	 * @param namespace
	 * @param name
	 * @param maxWaitMillis
	 * @return  The resource or null if the maxWaitMillis was exceeded or the resource doesnt exist
	 */
	public static IResource waitForResource(IClient client, String kind, String namespace, String name, long maxWaitMillis) {
		return waitForResource(client, kind, namespace, name, maxWaitMillis, new ReadyConditional() {
			@Override
			public boolean isReady(IResource resource) {
				return resource != null;
			}
			
		});
	}
	/**
	 * Wait for the resource to exist for cases where the test is faster
	 * then the server in reconciling its existence;
	 * 
	 * @param client
	 * @param kind
	 * @param namespace
	 * @param name
	 * @param maxWaitMillis
	 * @param conditional
	 * @return
	 */
	public static IResource waitForResource(IClient client, String kind, String namespace, String name, long maxWaitMillis, ReadyConditional conditional) {
		IResource resource = null;
		final long timeout = System.currentTimeMillis() + maxWaitMillis;
		do {
			try {
				resource = client.get(kind, name, namespace);
				if(resource != null && conditional != null) {
					if(conditional.isReady(resource)) {
						return resource;
					}
					resource = null;
				}
			}catch(NotFoundException e) {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e1) {
					throw new RuntimeException(e1);
				}
			}
		}while(resource == null && System.currentTimeMillis() <= timeout);
		return resource;
	}
	
	/**
	 * Interface that can evaluate a resource to determine if its ready
	 * @author jeff.cantrill
	 *
	 */
	public static interface ReadyConditional {
		
		/**
		 * 
		 * @param resource
		 * @return true if the resource is 'ready'
		 */
		boolean isReady(IResource resource);
	}

	
}
