/*
 * Copyright 2015-2019 the original author or authors.
 *
 * Licensed 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
 *
 *      https://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.springframework.cloud.deployer.spi.kubernetes;

import io.fabric8.kubernetes.api.model.ContainerStatus;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServicePort;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.deployer.spi.app.AppInstanceStatus;
import org.springframework.cloud.deployer.spi.app.DeploymentState;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Represents the status of a module.
 *
 * @author Florian Rosenberg
 * @author Thomas Risberg
 * @author David Turanski
 * @author Chris Schaefer
 */
public class KubernetesAppInstanceStatus implements AppInstanceStatus {

	private static Log logger = LogFactory.getLog(KubernetesAppInstanceStatus.class);
	private final Pod pod;
	private Service service;
	private KubernetesDeployerProperties properties;
	private ContainerStatus containerStatus;
	private RunningPhaseDeploymentStateResolver runningPhaseDeploymentStateResolver;

	@Deprecated
	public KubernetesAppInstanceStatus(Pod pod, Service service, KubernetesDeployerProperties properties) {
		this.pod = pod;
		this.service = service;
		this.properties = properties;
		// we assume one container per pod
		if (pod != null && pod.getStatus().getContainerStatuses().size() == 1) {
			this.containerStatus = pod.getStatus().getContainerStatuses().get(0);
		}
		else {
			this.containerStatus = null;
		}
		this.runningPhaseDeploymentStateResolver = new DefaultRunningPhaseDeploymentStateResolver(properties);
	}

	public KubernetesAppInstanceStatus(Pod pod, Service service, KubernetesDeployerProperties properties, ContainerStatus containerStatus) {
		this.pod = pod;
		this.service = service;
		this.properties = properties;
		this.containerStatus = containerStatus;
		this.runningPhaseDeploymentStateResolver = new DefaultRunningPhaseDeploymentStateResolver(properties);
	}

	/**
	 * Override the default {@link RunningPhaseDeploymentStateResolver} implementation.
	 *
	 * @param runningPhaseDeploymentStateResolver the {@link RunningPhaseDeploymentStateResolver} to use
	 */
	public void setRunningPhaseDeploymentStateResolver(
		RunningPhaseDeploymentStateResolver runningPhaseDeploymentStateResolver) {
		this.runningPhaseDeploymentStateResolver = runningPhaseDeploymentStateResolver;
	}

	@Override
	public String getId() {
		return pod == null ? "N/A" : pod.getMetadata().getName();
	}

	@Override
	public DeploymentState getState() {
		return pod != null && containerStatus != null ? mapState() : DeploymentState.unknown;
	}

	/**
	 * Maps Kubernetes phases/states onto Spring Cloud Deployer states
	 */
	private DeploymentState mapState() {
		logger.debug(String.format("%s - Phase [ %s ]", pod.getMetadata().getName(), pod.getStatus().getPhase()));
		logger.debug(String.format("%s - ContainerStatus [ %s ]", pod.getMetadata().getName(), containerStatus));
		switch (pod.getStatus().getPhase()) {

		case "Pending":
			return DeploymentState.deploying;

		// We only report a module as running if the container is also ready to service requests.
		// We also implement the Readiness check as part of the container to ensure ready means
		// that the module is up and running and not only that the JVM has been created and the
		// Spring module is still starting up
		case "Running":
			// we assume we only have one container
			return runningPhaseDeploymentStateResolver.resolve(containerStatus);
		case "Failed":
			return DeploymentState.failed;

		case "Unknown":
			return DeploymentState.unknown;

		default:
			return DeploymentState.unknown;
		}
	}

	@Override
	public Map<String, String> getAttributes() {
		Map<String, String> result = new HashMap<>();

		if (pod != null) {
			result.put("pod.name", pod.getMetadata().getName());
			result.put("pod.startTime", pod.getStatus().getStartTime());
			result.put("pod.ip", pod.getStatus().getPodIP());
			result.put("host.ip", pod.getStatus().getHostIP());
			result.put("phase", pod.getStatus().getPhase());
			result.put(AbstractKubernetesDeployer.SPRING_APP_KEY.replace('-', '.'),
				pod.getMetadata().getLabels().get(AbstractKubernetesDeployer.SPRING_APP_KEY));
			result.put(AbstractKubernetesDeployer.SPRING_DEPLOYMENT_KEY.replace('-', '.'),
				pod.getMetadata().getLabels().get(AbstractKubernetesDeployer.SPRING_DEPLOYMENT_KEY));
			result.put("guid", pod.getMetadata().getUid());
		}
		if (service != null) {
			result.put("service.name", service.getMetadata().getName());
			if ("LoadBalancer".equals(service.getSpec().getType())) {
				if (service.getStatus() != null && service.getStatus().getLoadBalancer() != null
					&& service.getStatus().getLoadBalancer().getIngress() != null && !service.getStatus()
					.getLoadBalancer().getIngress().isEmpty()) {
					String externalIp = service.getStatus().getLoadBalancer().getIngress().get(0).getIp();
					result.put("service.external.ip", externalIp);
					List<ServicePort> ports = service.getSpec().getPorts();
					int port = 0;
					if (ports != null && ports.size() > 0) {
						port = ports.get(0).getPort();
						result.put("service.external.port", String.valueOf(port));
					}
					if (externalIp != null) {
						result.put("url", "http://" + externalIp + (port > 0 && port != 80 ? ":" + port : ""));
					}

				}
			}
		}
		if (containerStatus != null) {
			result.put("container.restartCount", "" + containerStatus.getRestartCount());
			if (containerStatus.getLastState() != null && containerStatus.getLastState().getTerminated() != null) {
				result.put("container.lastState.terminated.exitCode",
					"" + containerStatus.getLastState().getTerminated().getExitCode());
				result.put("container.lastState.terminated.reason",
					containerStatus.getLastState().getTerminated().getReason());
			}
			if (containerStatus.getState() != null && containerStatus.getState().getTerminated() != null) {
				result.put("container.state.terminated.exitCode",
					"" + containerStatus.getState().getTerminated().getExitCode());
				result.put("container.state.terminated.reason", containerStatus.getState().getTerminated().getReason());
			}
		}
		return result;
	}
}