package com.agilex.healthcare.schedulingmanager.mdws.connection;

import java.io.IOException;
import java.io.Serializable;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Date;
import java.util.UUID;

import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;

import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;

import com.agilex.healthcare.mobilehealthplatform.domain.MhpUser;
import com.agilex.healthcare.mobilehealthplatform.security.AppUser;
import com.agilex.healthcare.schedulingmanager.mdws.MdwsResponseException;
import com.agilex.healthcare.schedulingmanager.mdws.generatedwsdl.emrservice.DataSourceArray;
import com.agilex.healthcare.schedulingmanager.mdws.generatedwsdl.emrservice.EmrSvc;
import com.agilex.healthcare.schedulingmanager.mdws.generatedwsdl.emrservice.EmrSvcSoap;

public class EmrConnection implements Serializable {
	private static final org.apache.commons.logging.Log logger = org.apache.commons.logging.LogFactory.getLog(EmrConnection.class);
	private static final long serialVersionUID = -7675397237993685048L;
	
	private String id;
	private MdwsConfiguration configuration;
	private EmrSvcSoap port;
	private Date refreshedTimestamp;

	public EmrConnection() {
		this.id = UUID.randomUUID().toString();
	}
	
	public EmrConnection(EmrSvcSoap port) {
		this.id = UUID.randomUUID().toString();
		this.port = port;
	}
	
	// this constructor will be removed once connection pool is adjusted
	@Deprecated
	public EmrConnection(MdwsConfiguration configuration) {
		this.id = UUID.randomUUID().toString();
		this.configuration = configuration;
		//this.authenticated = configuration.isAuthenticated();
		//this.port = establishConnection(configuration, authenticated);
		this.refreshedTimestamp = new Date();
	}
	
	/**
	 * Calls the MDWS service EmrSvc.connect if the authenticatedFlag is false - it will usually be true
	 * since most apps will login to a site with AuthorizationServices and that connection is used from that point on.
	 */
	public EmrConnection(MdwsConfiguration configuration, String siteCode, boolean authenticateFlag) {
		this.id = UUID.randomUUID().toString();
		this.configuration = configuration;
		this.port = establishConnection(configuration, siteCode, authenticateFlag);
		this.refreshedTimestamp = new Date();
	}
	
	public Date getRefreshedTimestamp() {
		return refreshedTimestamp;
	}

	public void refreshTimestamp() {
		this.refreshedTimestamp = new Date();
	}

	public String getId() {
		return id;
	}

	public EmrSvcSoap getPort() {
		return port;
	}
	
	
	public MdwsConfiguration getConfiguration() {
		return this.configuration;
	}
	
	/**
	 * Pings a HTTP URL and returns true if it didn't time out.
	 */
	private static boolean ping(String url, int timeout) {
		url = url.replaceFirst("https", "http"); // Otherwise an exception may be thrown on invalid SSL certificates.

		HttpURLConnection connection = null;
		try {
			connection = (HttpURLConnection) new URL(url).openConnection();
			connection.setConnectTimeout(timeout);
			connection.setReadTimeout(timeout);
			connection.setRequestMethod("HEAD");
			connection.getResponseCode(); //NOTE: We are getting a 405 responseCode from mdws - but that just proves that the server is up (which is what we care about)
			return true;
		} catch (IOException exception) {
			return false;
		} finally {
			if (connection != null) {
				try { connection.disconnect(); } catch (Exception e) { logger.info("Problem disconnecting from ping"); }
			}
		}
	}
	
	public static EmrSvcSoap establishConnection(MdwsConfiguration configuration, String siteCode, boolean authenticated) {
		String baseUrl = "";
		MhpUser mhpUser = null;
		
		if (authenticated){
			mhpUser = getMhpUser();
			baseUrl = mhpUser.getMdwsInfo().getBaseUrl();
		}else{
			baseUrl = configuration.getEndPointBaseUrl();
		}
		
		String url = baseUrl + configuration.getEndPointUrl();
		
		if (configuration.isPingWsdlBeforeConnect() && ping(url, configuration.getWsConnectTimeout()) == false)
			throw new MdwsResponseException(MdwsResponseException.UserMessages.CONNECTION_ERROR, "Unable to reach the server at " + url);
		
		URL wsdlLocation = constructWsdlUrl(url);
		EmrSvc emrService = null;
		try {
			emrService = new EmrSvc(wsdlLocation, new QName("http://mdws.domain/EmrSvc", "EmrSvc"));
		} catch (Throwable t) {
			throw new MdwsResponseException(MdwsResponseException.UserMessages.CONNECTION_ERROR, t.getMessage());
		}
		EmrSvcSoap port = emrService.getEmrSvcSoap();
		
		((BindingProvider)port).getRequestContext().put(BindingProvider.SESSION_MAINTAIN_PROPERTY, true);
		((BindingProvider)port).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, url);	    
		((BindingProvider)port).getRequestContext().put("javax.xml.ws.client.connectionTimeout", configuration.getWsConnectTimeout());
		((BindingProvider)port).getRequestContext().put("javax.xml.ws.client.receiveTimeout", configuration.getWsRequestTimeout());
		
		if (authenticated){
			setSessionOnPort(port, mhpUser.getMdwsInfo().getSessionId());
		}else{
			 DataSourceArray dsa = null;

			 try {
	        	dsa = port.connect(siteCode);
	        } catch (Throwable t) {
				throw new MdwsResponseException(MdwsResponseException.UserMessages.CONNECTION_ERROR, t.getMessage());
			}
	        if (dsa.getFault() != null)
				throw new MdwsResponseException(MdwsResponseException.UserMessages.CONNECTION_ERROR, dsa.getFault());
		}

		return port;
	}

	private static URL constructWsdlUrl(String endPointUrl){
		String wsdlLocation = endPointUrl + "?WSDL";
		URL wsdlLocationURL = null;
		try {
			wsdlLocationURL = new URL(wsdlLocation);
		} catch (Exception e) {
			throw new MdwsResponseException(MdwsResponseException.UserMessages.INVALID_WSDL, e.getMessage());
		}
		
		return wsdlLocationURL;
	}


	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		EmrConnection other = (EmrConnection) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		return true;
	}
	
	public static MhpUser getMhpUser(){
        SecurityContext ctx = SecurityContextHolder.getContext();
        if (ctx == null)
        	throw new MdwsResponseException(MdwsResponseException.UserMessages.CONNECTION_ERROR, "Unable to get Spring Context");
        if (ctx.getAuthentication() == null)
        	throw new MdwsResponseException(MdwsResponseException.UserMessages.CONNECTION_ERROR, "Unable to get Spring Authentication");
        if (ctx.getAuthentication().getPrincipal() == null)
        	throw new MdwsResponseException(MdwsResponseException.UserMessages.CONNECTION_ERROR, "Unable to get Spring Principal");
        if (((AppUser)ctx.getAuthentication().getPrincipal()).getMhpUser() == null)
        	throw new MdwsResponseException(MdwsResponseException.UserMessages.CONNECTION_ERROR, "Unable to get Spring MhpUser");
        
        
        AppUser appUser = (AppUser) ctx.getAuthentication().getPrincipal();
        MhpUser mhpUser = appUser.getMhpUser();
        mhpUser.setUserName(appUser.getUsername());

        return mhpUser;
	}
	
	private static void setSessionOnPort(EmrSvcSoap port, String sessionId) {
		Client client = ClientProxy.getClient(port);

		HTTPClientPolicy policy = new HTTPClientPolicy();
		HTTPConduit httpConduit = (HTTPConduit)client.getConduit();
		
		policy.setCookie("ASP.NET_SessionId=" + sessionId);
		
		httpConduit.setClient(policy);
	}
}
