/*
 *  Copyright 2013-2017 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
 *
 *       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.springframework.cloud.contract.stubrunner.spring.cloud;

import java.lang.invoke.MethodHandles;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.contract.stubrunner.RunningStubs;
import org.springframework.cloud.contract.stubrunner.StubFinder;
import org.springframework.cloud.contract.stubrunner.util.StringUtils;

/**
 * Custom version of {@link DiscoveryClient} that tries to find an instance
 * in one of the started WireMock servers
 *
 * @author Marcin Grzejszczak
 *
 * @since 1.0.0
 */
class StubRunnerDiscoveryClient implements DiscoveryClient {

	private static final Log log = LogFactory.getLog(MethodHandles.lookup().lookupClass());

	private final DiscoveryClient delegate;
	private final StubFinder stubFinder;
	private final StubMapperProperties stubMapperProperties;

	public StubRunnerDiscoveryClient(DiscoveryClient delegate, StubFinder stubFinder,
			StubMapperProperties stubMapperProperties, String springAppName) {
		this.delegate = delegate instanceof StubRunnerDiscoveryClient ?
				noOpDiscoveryClient(springAppName) : delegate;
		if (log.isDebugEnabled()) {
			log.debug("Will delegate calls to discovery service [" + this.delegate + "] if a stub is not found");
		}
		this.stubFinder = stubFinder;
		this.stubMapperProperties = stubMapperProperties;
	}

	public StubRunnerDiscoveryClient(StubFinder stubFinder,
			StubMapperProperties stubMapperProperties, String springAppName) {
		this.delegate = noOpDiscoveryClient(springAppName);
		if (log.isDebugEnabled()) {
			log.debug("Will delegate calls to discovery service [" + this.delegate + "] if a stub is not found");
		}
		this.stubFinder = stubFinder;
		this.stubMapperProperties = stubMapperProperties;
	}

	private StubRunnerNoOpDiscoveryClient noOpDiscoveryClient(String springAppName) {
		return new StubRunnerNoOpDiscoveryClient(new DefaultServiceInstance(springAppName, "localhost", 0, false));
	}

	@Override
	public String description() {
		try {
			return this.delegate.description();
		} catch (Exception e) {
			if (log.isDebugEnabled()) {
				log.debug("Failed to fetch description from delegate", e);
			}
		}
		return "";
	}

	@Override
	public ServiceInstance getLocalServiceInstance() {
		try {
			return this.delegate.getLocalServiceInstance();
		} catch (Exception e) {
			if (log.isDebugEnabled()) {
				log.debug("Failed to get local service instance from delegate", e);
			}
		}
		return null;
	}

	@Override
	public List<ServiceInstance> getInstances(String serviceId) {
		String ivyNotation = this.stubMapperProperties.fromServiceIdToIvyNotation(serviceId);
		String serviceToFind = StringUtils.hasText(ivyNotation) ? ivyNotation : serviceId;
		URL stubUrl = this.stubFinder.findStubUrl(serviceToFind);
		log.info("Resolved from ivy [" + ivyNotation + "] service to find [" + serviceToFind + "]. "
				+ "Found stub is available under URL [" + stubUrl + "]");
		if (stubUrl == null) {
			return getInstancesFromDelegate(serviceId);
		}
		return Collections.<ServiceInstance>singletonList(
				new StubRunnerServiceInstance(serviceId, stubUrl.getHost(), stubUrl.getPort(), toUri(stubUrl))
		);
	}

	private List<ServiceInstance> getInstancesFromDelegate(String serviceId) {
		try {
			return new ArrayList<>(this.delegate.getInstances(serviceId));
		} catch (Exception e) {
			if (log.isDebugEnabled()) {
				log.debug("Failed to fetch instances from delegate", e);
			}
			return new ArrayList<>();
		}
	}

	private URI toUri(URL url) {
		try {
			return url.toURI();
		} catch (Exception e) {
			return null;
		}
	}

	@Override
	public List<String> getServices() {
		List<String> list = new ArrayList<>();
		List<String> services = getServicesFromDelegate();
		RunningStubs runningStubs = this.stubFinder.findAllRunningStubs();
		list.addAll(services);
		list.addAll(runningStubs.getAllServicesNames());
		return list;
	}

	private List<String> getServicesFromDelegate() {
		try {
			return new ArrayList<>(this.delegate.getServices());
		} catch (Exception e) {
			if (log.isDebugEnabled()) {
				log.debug("Failed to fetch services from delegate", e);
			}
		}
		return new ArrayList<>();
	}
}

class StubRunnerNoOpDiscoveryClient implements DiscoveryClient {

	private final ServiceInstance instance;

	public StubRunnerNoOpDiscoveryClient(ServiceInstance instance) {
		this.instance = instance;
	}

	@Override
	public String description() {
		return "Spring Cloud Stub Runner No-op DiscoveryClient";
	}

	@Override
	public ServiceInstance getLocalServiceInstance() {
		return this.instance;
	}

	@Override
	public List<ServiceInstance> getInstances(String serviceId) {
		return Collections.emptyList();
	}

	@Override
	public List<String> getServices() {
		return Collections.emptyList();
	}
}

@@ -946,72 +946,8 @@
 ry;%0A
-import org.springframework.cloud.client.DefaultServiceInstance;%0A
 impo
@@ -1981,29 +1981,16 @@
 yClient(
-springAppName
 ) : dele
@@ -2387,29 +2387,16 @@
 yClient(
-springAppName
 );%0A%09%09if 
@@ -2676,36 +2676,16 @@
 yClient(
-String springAppName
 ) %7B%0A%09%09re
@@ -2727,72 +2727,8 @@
 ent(
-new DefaultServiceInstance(springAppName, %22localhost%22, 0, false)
 );%0A%09
@@ -2969,287 +2969,8 @@
 %09%7D%0A%0A
-%09@Override%0A%09public ServiceInstance getLocalServiceInstance() %7B%0A%09%09try %7B%0A%09%09%09return this.delegate.getLocalServiceInstance();%0A%09%09%7D catch (Exception e) %7B%0A%09%09%09if (log.isDebugEnabled()) %7B%0A%09%09%09%09log.debug(%22Failed to get local service instance from delegate%22, e);%0A%09%09%09%7D%0A%09%09%7D%0A%09%09return null;%0A%09%7D%0A%0A
 %09@Ov
@@ -4737,148 +4737,8 @@
  %7B%0A%0A
-%09private final ServiceInstance instance;%0A%0A%09public StubRunnerNoOpDiscoveryClient(ServiceInstance instance) %7B%0A%09%09this.instance = instance;%0A%09%7D%0A%0A
 %09@Ov
@@ -4842,99 +4842,8 @@
 %09%7D%0A%0A
-%09@Override%0A%09public ServiceInstance getLocalServiceInstance() %7B%0A%09%09return this.instance;%0A%09%7D%0A%0A
 %09@Ov

Remove uses of DiscoveryClient.getLocalServiceInstance()