package com.hotent.assembly.mockmvc.client;

import static feign.Util.UTF_8;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.util.EntityUtils;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import com.hotent.base.exception.BaseException;
import com.hotent.base.util.ThreadMsgUtil;

import feign.Client;
import feign.Request;
import feign.Request.Options;
import feign.Response;
import feign.Response.Builder;
import feign.Util;

/**
 * 
 * @author jason
 * @date 2021-01-26
 *
 */
public final class MockMvcClient implements Client {
	private static final String ACCEPT_HEADER_NAME = "Accept";

	private MockMvc mockMvc;

	public MockMvcClient(MockMvc mockMvc) {
		this.mockMvc = mockMvc;  
	}

	@Override
	public Response execute(Request request, Request.Options options) throws IOException {
		MockHttpServletResponse response = null;
		try {
			ThreadMsgUtil.incrementEnter();
			org.springframework.test.web.servlet.RequestBuilder requestBuilder = null;
			requestBuilder = getRequestBuilder(request,options);
			MvcResult andReturn = mockMvc.perform(requestBuilder).andReturn();
			response = andReturn.getResponse();
		} catch (ServletException | IOException e) {
			throw new BaseException("调用feign方法异常", e);
		} catch (Exception e) {
			throw new BaseException("调用feign方法异常", e);
		}finally {
			ThreadMsgUtil.reduceEnter();
		}
		return toFeignResponse(response, request);

	}
	
	/**
	 * 只针对get请求进行缓存
	 * @param request
	 * @param options
	 * @return
	 * @throws Exception
	 */
	private org.springframework.test.web.servlet.RequestBuilder getRequestBuilder(Request request,Request.Options options) throws Exception {
		return createRequestBuilder(request, options);
	}

	Response toFeignResponse(HttpResponse httpResponse, Request request) throws IOException {
		StatusLine statusLine = httpResponse.getStatusLine();
		int statusCode = statusLine.getStatusCode();

		String reason = statusLine.getReasonPhrase();

		Map<String, Collection<String>> headers = new HashMap<String, Collection<String>>();
		for (Header header : httpResponse.getAllHeaders()) {
			String name = header.getName();
			String value = header.getValue();

			Collection<String> headerValues = headers.get(name);
			if (headerValues == null) {
				headerValues = new ArrayList<String>();
				headers.put(name, headerValues);
			}
			headerValues.add(value);
		}

		return Response.builder().status(statusCode).reason(reason).headers(headers).request(request)
				.body(toFeignBody(httpResponse)).build();
	}

	Response.Body toFeignBody(HttpResponse httpResponse) {
		final HttpEntity entity = httpResponse.getEntity();
		if (entity == null) {
			return null;
		}
		return new Response.Body() {

			@Override
			public Integer length() {
				return entity.getContentLength() >= 0 && entity.getContentLength() <= Integer.MAX_VALUE
						? (int) entity.getContentLength()
						: null;
			}

			@Override
			public boolean isRepeatable() {
				return entity.isRepeatable();
			}

			@Override
			public InputStream asInputStream() throws IOException {
				return entity.getContent();
			}

			@Override
			public Reader asReader() throws IOException {
				return new InputStreamReader(asInputStream(), UTF_8);
			}

			@Override
			public Reader asReader(Charset charset) throws IOException {
				Util.checkNotNull(charset, "charset should not be null");
				return new InputStreamReader(asInputStream(), charset);
			}

			@Override
			public void close() throws IOException {
				EntityUtils.consume(entity);
			}
		};
	}

	private Response toFeignResponse(MockHttpServletResponse response, Request request) {

		int statusCode = response.getStatus();

		String reason = response.getErrorMessage();

		Map<String, Collection<String>> headers = new HashMap<String, Collection<String>>();
		for (String name : response.getHeaderNames()) {
			String value = response.getHeader(name);

			Collection<String> headerValues = headers.get(name);
			if (headerValues == null) {
				headerValues = new ArrayList<String>();
				headers.put(name, headerValues);
			}
			headerValues.add(value);
		}
		Builder builder = Response.builder().body(response.getContentAsByteArray());
		return builder.status(statusCode).reason(reason).headers(headers).request(request).build();

	}

	private org.springframework.test.web.servlet.RequestBuilder createRequestBuilder(Request request, Options options)
			throws URISyntaxException {

		RequestBuilder requestBuilder = RequestBuilder.create(request.httpMethod().name());
		URI uri = new URIBuilder(request.url()).build();
		requestBuilder.setUri(uri.getScheme() + "://" + uri.getAuthority() + uri.getRawPath());
		MockHttpServletRequestBuilder mockMvcRequestBuilders = createRequestBuilder(request.httpMethod().name(),
				request.url());
		
		// request query params url 有传递参数
		List<NameValuePair> queryParams = URLEncodedUtils.parse(uri, requestBuilder.getCharset());
		for (NameValuePair queryParam : queryParams) {
			mockMvcRequestBuilders.param(queryParam.getName(), queryParam.getValue());
		}

		// request headers
		boolean hasAcceptHeader = false;
		for (Map.Entry<String, Collection<String>> headerEntry : request.headers().entrySet()) {
			String headerName = headerEntry.getKey();
			if (headerName.equalsIgnoreCase(ACCEPT_HEADER_NAME)) {
				hasAcceptHeader = true;
			}

			if (headerName.equalsIgnoreCase(Util.CONTENT_LENGTH)) {
				// The 'Content-Length' header is always set by the Apache client and it
				// doesn't like us to set it as well.
				continue;
			}

			for (String headerValue : headerEntry.getValue()) {
				mockMvcRequestBuilders.header(headerName, headerValue);
			}
		}
		// some servers choke on the default accept string, so we'll set it to anything
		if (!hasAcceptHeader) {
			mockMvcRequestBuilders.header(ACCEPT_HEADER_NAME, "*/*");
		}

		// request body
		if (request.body() != null) {
			if (request.charset() != null) {
				String content = new String(request.body(), request.charset());
				mockMvcRequestBuilders.contentType(MediaType.APPLICATION_JSON).content(content);
			} else {
				mockMvcRequestBuilders.contentType(MediaType.APPLICATION_JSON).content(request.body());
			}
		} else {
			mockMvcRequestBuilders.contentType(MediaType.APPLICATION_JSON).content(new byte[0]);
		}

		return mockMvcRequestBuilders;

	}

	/**
	 * 享元 优化性能
	 * 
	 * @param name
	 * @param url
	 * @return
	 * @throws URISyntaxException
	 */
	private MockHttpServletRequestBuilder createRequestBuilder(String name, String url)
			throws URISyntaxException {
		// GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
		name = name.toUpperCase();
		MockHttpServletRequestBuilder mockHttpServletRequestBuilder = null;
		if (url.indexOf("?") != -1) {
			url = url.substring(0, url.indexOf("?"));
		}

		switch (name) {
		case "GET":
			mockHttpServletRequestBuilder = MockMvcRequestBuilders.get(url);
			break;
		case "POST":
			mockHttpServletRequestBuilder = MockMvcRequestBuilders.post(url);
			break;
		case "HEAD":
			mockHttpServletRequestBuilder = MockMvcRequestBuilders.head(url);
			break;
		case "PUT":
			mockHttpServletRequestBuilder = MockMvcRequestBuilders.put(url);
			break;
		case "PATCH":
			mockHttpServletRequestBuilder = MockMvcRequestBuilders.patch(url);
			break;
		case "DELETE":
			mockHttpServletRequestBuilder = MockMvcRequestBuilders.delete(url);
			break;
		case "OPTIONS":
			mockHttpServletRequestBuilder = MockMvcRequestBuilders.options(url);
			break;
		default:
			break;
		}
		return mockHttpServletRequestBuilder;

	}
}
