package com.ejie.ad74a.util.comun;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.Enumeration;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.ejie.ad74a.exceptions.Ad74aException;

/**
 * Agrupa utilidades especificas de parte WEB.
 * 
 * @author Ibermatica
 * 
 */
public class UtilWeb {
	private static final Logger logger = LoggerFactory.getLogger(UtilWeb.class);

	private static final String DEFAULT_CONTENTTYPE = "application/octet-stream";
	private static final int LENGTH_AVG_TRAZA = 128;

	/**
	 * Constructor privado sin parametros
	 */
	private UtilWeb() {
	}

	/**
	 * Carga un archivo en la response, para que se pueda descargar desde el
	 * navegador. Acepta definicion de cookie para descarga ajax.
	 * 
	 * @param filename     String
	 * @param contenttype  String
	 * @param size         long
	 * @param inputcontent InputStream
	 * @param response     HttpServletResponse
	 * @param cookie       String (Ej.: "ajaxDownloadFile=true; path=/")
	 * @throws IOException e
	 */
	public static void downloadFile(String filename, String contenttype, long size, InputStream inputcontent,
			HttpServletResponse response, String cookie) throws IOException {

		if (UtilWeb.logger.isTraceEnabled()) {
			final String sT = ", ";
			StringBuilder traza = new StringBuilder(UtilWeb.LENGTH_AVG_TRAZA);
			traza.append("downloadFile [");
			traza.append(filename).append(sT);
			traza.append(contenttype).append(sT);
			traza.append(size).append(sT);
			traza.append(inputcontent).append(sT);
			traza.append(response).append("]");
			UtilWeb.logger.trace(traza.toString());
		}

		if (size != 0) {
			response.addHeader("Content-Length", Long.toString(size));
		}

		if (!Utilidades.isEmpty(contenttype)) {
			if ("text/html".equalsIgnoreCase(contenttype))
				response.setCharacterEncoding("ISO-8859-1");
			response.setContentType(contenttype);
		} else {
			response.setContentType(UtilWeb.DEFAULT_CONTENTTYPE);
		}

		response.setHeader("Content-Disposition",
				new StringBuilder().append("inline; filename=\"").append(filename).append("\"").toString());

		// Cookie para descarga Ajax
		if (Utilidades.isEmpty(cookie)) {
			response.setHeader("Set-Cookie", cookie);
		}

		if (inputcontent != null) {
			stream(inputcontent, response.getOutputStream());
		}

	}

	/**
	 * Recoge el valor de un parametro de la request que acaba con el nombre
	 * especifiado.<br/>
	 * Util para objetos que pueden ser atributos de varios objetos a la vez.
	 * 
	 * @param paramNameEnding String
	 * @param request         HttpServletRequest
	 * @return String
	 */
	public static String getRequestParamEndsLike(String paramNameEnding, HttpServletRequest request) {
		String resultado = null;
		Enumeration<String> parameters = request.getParameterNames();
		String auxName;
		while (parameters.hasMoreElements() && resultado == null) {
			auxName = parameters.nextElement();
			if (auxName.endsWith(paramNameEnding)) {
				resultado = request.getParameter(auxName);
			}
		}
		return resultado;
	}

	/**
	 * Traspasa informacion de un stream de entrada a uno de salida, de forma 'no
	 * bloqueante' (usando la libreria nio).
	 * 
	 * @param input  InputStream
	 * @param output OutputStream
	 * @return long
	 * @throws IOException e
	 */
	public static long stream(InputStream input, OutputStream output) throws IOException {
		ReadableByteChannel inputChannel = null;
		WritableByteChannel outputChannel = null;

		try {
			inputChannel = Channels.newChannel(input);
			outputChannel = Channels.newChannel(output);
			ByteBuffer buffer = ByteBuffer.allocate(UtilWeb.STREAM_ALLOCATE_BUFFER_SIZE);
			long size = 0;

			while (inputChannel.read(buffer) != -1) {
				buffer.flip();
				size += outputChannel.write(buffer);
				buffer.clear();
			}
			return size;
		} finally {
			if (outputChannel != null) {
				try {
					outputChannel.close();
				} catch (IOException e) {
					UtilWeb.logger.error("Error al cerrar canal de salida", e);
				}
			}
			if (inputChannel != null) {
				try {
					inputChannel.close();
				} catch (IOException e) {
					UtilWeb.logger.error("Error al cerrar canal de entrada", e);
				}
			}
		}
	}

	private static final int STREAM_ALLOCATE_BUFFER_SIZE = 1024;

	/**
	 * Prepara y envia una peticion http, gestionando posibles problemas de CORS, o
	 * de envio de cookies.
	 * <p>
	 * <b>¡Atención! indicar el parámetro <code>sendCurrentCookies</code> a
	 * <code>"TRUE"</code> únicamente en peticiones realizadas por el usuario, no
	 * desde procesos batch/desatendidos. </b>
	 * </p>
	 * 
	 * @param url                Direccion a invocar
	 * @param httpMethod         Método con el que invocar (GET,POST,PUT,....)
	 * @param fixCORS            Indicar si se quiere modificar la cabecera "Origin"
	 *                           para evitar problemas de CORS
	 * @param sendCurrentCookies Indicar si se quieren enviar las cookies recibidas
	 *                           en la request actual del usuario
	 * @return Objeto HttpURLConnection ya enviado
	 * @throws IOException
	 * @author JBHG
	 * @deprecated [jbhg] No usar con parámetro "fixCORS". Ademas solo se deberían
	 *             abrir conexiones contra endpoints que estén publicados en el OSB
	 *             de Gobierno, de otro modo se corre el riesgo de que el servidor
	 *             no tenga conectividad con el endpoint indicado.
	 */
	@Deprecated
	public static HttpURLConnection sendHttpURLConnection(URL url, String httpMethod, boolean fixCORS,
			boolean sendCurrentCookies) throws IOException {
		String method = "GET";

		URLConnection urlconnection = url.openConnection();
		fixSslProblems(urlconnection);
		HttpURLConnection httpConn = (HttpURLConnection) urlconnection;
		if (StringUtils.isNotBlank(httpMethod)) {
			method = httpMethod.toUpperCase();
		}
		httpConn.setRequestMethod(method);
		if (fixCORS) {
			// XXX [jbhg] para permitir llamadas con CORS debemos modificar la cabecera
			// "Origin" para que coincida con el dominio de la url a invocar. Para ello hay
			// que
			// quitar la restriccion que JAVA pone para modificar dicha cabecera.
			logger.debug("sendHttpURLConnection sun.net.http.allowRestrictedHeaders={}",
					System.getProperty("sun.net.http.allowRestrictedHeaders"));
			if (!"true".equals(System.getProperty("sun.net.http.allowRestrictedHeaders"))) {
				System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
				logger.debug("sendHttpURLConnection sun.net.http.allowRestrictedHeaders setted TRUE");
			}
			String origin = url.getProtocol() + "://" + url.getHost();
			origin += (url.getPort() < 0) ? "" : ":" + url.getPort();

			logger.debug("sendHttpURLConnection modificacion cabecera Origin={}", origin);
			httpConn.setRequestProperty("Origin", origin);
		}
		if (sendCurrentCookies) {
			// Para urls que necesiten la sesion actual de xlnets,... compartimos las
			// cookies de la
			// request actual
			ServletRequestAttributes reqAtt = (ServletRequestAttributes) RequestContextHolder
					.currentRequestAttributes();
			httpConn.setRequestProperty("Cookie", reqAtt.getRequest().getHeader("Cookie"));
		}
		int code = httpConn.getResponseCode();// Send request
		logger.trace("sendHttpURLConnection request: {} [{}] -> response: [{}] {}", url, method, code,
				httpConn.getResponseMessage());
		return httpConn;
	}

	/*-
	 *  Objeto para ignorar los problemas de certificados inválidos/caducados.
	 */
	private static TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
		public java.security.cert.X509Certificate[] getAcceptedIssuers() {
			return new X509Certificate[0];
		}

		public void checkClientTrusted(X509Certificate[] certs, String authType) {
			// todo confiable
		}

		public void checkServerTrusted(X509Certificate[] certs, String authType) {
			// todo confiable
		}

	} };
	private static boolean fixSslApplied = false;

	/*
	 * Arregla problemas de certificados caducados/inválidos en las llamadas desde
	 * servidor. TODO Esta funcionalidad debería aplicarse solo en los entornos de
	 * DESARROLLO Y PRUEBAS. Se supone que en PRODUCCION todos los sitios deberían
	 * poseer un certificado válido.
	 */
	private static void fixSslProblems(URLConnection urlConnection) {
		/* Start of Fix */
		if (!fixSslApplied) {
			try {
				SSLContext sc = SSLContext.getInstance("TLS");
				sc.init(null, trustAllCerts, new java.security.SecureRandom());
				HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
				weblogic.net.http.HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

				// Install the all-trusting host verifier
				HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true);
			} catch (NoSuchAlgorithmException | KeyManagementException e) {
				throw new Ad74aException(e, "SSL Error");
			}
			fixSslApplied = true;
		}
		/*
		 * Fix para la clase de weblogic (se aplica sobre la conexion actual) (solo si
		 * el servidor weblogic esta usando clases propias de HttpsURLConnection)
		 */
		try {
			Class.forName("weblogic.net.http.HttpsURLConnection", false, UtilWeb.class.getClassLoader());
			if (urlConnection instanceof weblogic.net.http.HttpsURLConnection) {
				((weblogic.net.http.HttpsURLConnection) urlConnection).setTrustManager((arg0, arg1) -> false);
			}
		} catch (ClassNotFoundException e) {
			// No aplicar el parche a la clase de weblogic
		}
		/* End of the fix */

	}
}
