package jenkins.plugins.mattermost;

import hudson.ProxyConfiguration;
import jenkins.model.Jenkins;
import org.apache.http.HttpHost;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.DefaultProxyRoutePlanner;
import org.apache.http.ssl.SSLContexts;
import org.json.JSONArray;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class StandardMattermostService implements MattermostService
{

	private static final Logger logger = Logger.getLogger(StandardMattermostService.class.getName());

	private String endpoint;
	private final String[] channelIds;
	private final String icon;

	public StandardMattermostService(String endpoint, String channelId, String icon)
	{
		super();
		this.endpoint = endpoint;
		this.channelIds = channelId.split("[,;]+");
		this.icon = icon;
	}

	private static JSONObject createPayload(String message, String text, String color, String roomId, String userId, String icon)
	{
		JSONObject json = new JSONObject();
		JSONObject field = new JSONObject();
		field.put("short", false);
		field.put("value", message);
		JSONArray fields = new JSONArray();
		fields.put(field);

		JSONObject attachment = new JSONObject();
		attachment.put("fallback", message);
		attachment.put("color", color);
		attachment.put("fields", fields);
		JSONArray mrkdwn = new JSONArray();
		mrkdwn.put("pretext");
		mrkdwn.put("text");
		mrkdwn.put("fields");
		attachment.put("mrkdwn_in", mrkdwn);
		JSONArray attachments = new JSONArray();
		attachments.put(attachment);
		json.put("text", text);
		json.put("attachments", attachments);

		if (!roomId.isEmpty()) json.put("channel", roomId);
		json.put("username", userId);
		json.put("icon_url", icon);
		return json;
	}

	public static String createRegexFromGlob(String glob)
	{
		StringBuilder out = new StringBuilder("^");
		for (int i = 0; i < glob.length(); ++i)
		{
			final char c = glob.charAt(i);
			switch (c)
			{
				case '*':
					out.append(".*");
					break;
				case '?':
					out.append('.');
					break;
				case '.':
					out.append("\\.");
					break;
				case '\\':
					out.append("\\\\");
					break;
				default:
					out.append(c);
			}
		}
		out.append('$');
		return out.toString();
	}

	public boolean publish(String message)
	{
		return publish(message, "warning");
	}

	public boolean publish(String message, String color)
	{
		return publish(message, "", color);
	}

	public boolean publish(String message, String text, String color)
	{
		boolean result = true;
		for (String userAndRoomId : channelIds)
		{
			//String url = endpoint;
			URL url;
			try
			{

				String roomId = userAndRoomId.trim();
				String userId = "jenkins";
				url = new URL(this.endpoint);
				HttpHost httpHost = new HttpHost(url.getHost(), url.getPort(), url.getProtocol());
				HttpClientBuilder clientBuilder = HttpClients.custom();
				clientBuilder.setSSLContext(SSLContexts.createDefault());
				RequestConfig.Builder reqconfigconbuilder = RequestConfig.custom();
				reqconfigconbuilder.setConnectTimeout(10000);
				reqconfigconbuilder.setSocketTimeout(10000);

				ProxyConfiguration proxy = Jenkins.get().proxy;
				if (proxy != null && isProxyRequired(ProxyConfiguration.getNoProxyHostPatterns(proxy.noProxyHost)))
				{
					setupProxy(proxy, clientBuilder, reqconfigconbuilder);
				}

				RequestConfig config = reqconfigconbuilder.build();
				CloseableHttpClient client = clientBuilder.build();
				RequestBuilder requestBuilder = RequestBuilder.post(url.toURI());
				requestBuilder.setConfig(config);
				requestBuilder.setCharset(StandardCharsets.UTF_8);

				// Supported channel string formats:
				// - user@channel
				// - user@@dmchannel
				// - channel
				// - @dmchannel
				int atPos = userAndRoomId.indexOf("@");
				if (atPos > 0 && atPos < userAndRoomId.length() - 1)
				{
					userId = userAndRoomId.substring(0, atPos).trim();
					roomId = userAndRoomId.substring(atPos + 1).trim();
				}

				String roomIdString = roomId;
				if (roomIdString.isEmpty())
				{
					roomIdString = "(default)";
				}


				JSONObject json = createPayload(message, text, color, roomId, userId, icon);
				logger.info("Playload: " + json.toString());
				requestBuilder.setEntity(new StringEntity(json.toString(), ContentType.APPLICATION_JSON));
				CloseableHttpResponse execute = client.execute(httpHost, requestBuilder.build());
				int responseCode = execute.getStatusLine().getStatusCode();
				if (responseCode != HttpStatus.SC_OK)
				{
					result = false;
					logHttpErrorStatus(execute, responseCode, roomIdString, url);
				} else
					logger.info("Status " + responseCode + ": to " + roomIdString + "@" + url.getHost() + "/***: " + message + " (" + color + ")");
			} catch(java.net.URISyntaxException | java.io.IOException e)
			{
				logger.log(Level.WARNING, "Error posting to Mattermost", e);
				result = false;
			}
		}
		return result;
	}

	private void logHttpErrorStatus(CloseableHttpResponse execute, int responseCode, String roomIdString, URL hosturl) throws IOException
	{
		BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(execute.getEntity().getContent(), Charset.defaultCharset()));
		try {
			String collect = bufferedReader.lines().collect(Collectors.joining(" "));
			logger.log(Level.WARNING, "WARN Status " + responseCode + ": to " + roomIdString + "@" + hosturl.getHost() + ": " + collect);
		} finally {
			bufferedReader.close();
		}
	}

	private RequestConfig.Builder setupProxy(ProxyConfiguration proxy, HttpClientBuilder clientBuilder, RequestConfig.Builder reqconfigconbuilder) throws MalformedURLException
	{
		HttpHost proxyHost = new HttpHost(proxy.name, proxy.port);
		DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxyHost);
		clientBuilder.setRoutePlanner(routePlanner);
		reqconfigconbuilder.setProxy(proxyHost);

		setupProxyAuth(proxy, clientBuilder, proxyHost);
		return reqconfigconbuilder;
	}

	private void setupProxyAuth(ProxyConfiguration proxy, HttpClientBuilder clientBuilder, HttpHost proxyHost)
	{
		String username = proxy.getUserName();
		String password = proxy.getPassword();
		// Consider it to be passed if username specified. Sufficient?
		if (username != null && !username.isEmpty())
		{
			logger.info("Using proxy authentication (user=" + username + ")");
			BasicCredentialsProvider basicCredentialsProvider = new BasicCredentialsProvider();
			basicCredentialsProvider.setCredentials(
					new org.apache.http.auth.AuthScope(proxyHost.getHostName(), proxy.port),
					new org.apache.http.auth.UsernamePasswordCredentials(username, password));

			clientBuilder.setDefaultCredentialsProvider(basicCredentialsProvider);
		}
	}

	protected boolean isProxyRequired(List<Pattern> noProxyHosts)
	{
		try
		{
			URL url = new URL(endpoint);
			for (Pattern p : noProxyHosts)
			{
				if (p.matcher(url.getHost()).matches()) return false;
			}
		} catch (MalformedURLException e)
		{
			logger.log(
					Level.WARNING,
					"A malformed URL [" + endpoint + "] is defined as endpoint, please check your settings");
			// default behavior : proxy still activated
			return true;
		}
		return true;
	}

	@Deprecated
	protected boolean isProxyRequired(String... noProxyHost)
	{//
		if (noProxyHost == null)
			return false;
		List<String> lst = Arrays.asList(noProxyHost);
		List<Pattern> collect = lst.stream()
				.filter(Objects::nonNull)
				.map(StandardMattermostService::createRegexFromGlob)
				.map(Pattern::compile)
				.collect(Collectors.toList());
		return isProxyRequired(collect);
	}

	void setEndpoint(String endpoint)
	{
		this.endpoint = endpoint;
	}
}