package org.gcube.contentmanager.storageserver.startup;

import static org.gcube.resources.discovery.icclient.ICFactory.clientFor;
import static org.gcube.resources.discovery.icclient.ICFactory.queryFor;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.gcube.common.encryption.encrypter.StringEncrypter;
import org.gcube.common.resources.gcore.GCoreEndpoint;
import org.gcube.common.resources.gcore.GCoreEndpoint.Profile.Endpoint;
import org.gcube.common.resources.gcore.ServiceEndpoint;
import org.gcube.common.resources.gcore.ServiceEndpoint.AccessPoint;
import org.gcube.common.resources.gcore.ServiceEndpoint.Property;
import org.gcube.common.resources.gcore.utils.Group;
import org.gcube.common.scope.api.ScopeProvider;
import org.gcube.contentmanager.storageserver.parse.utils.ValidationUtils;
import org.gcube.resources.discovery.client.api.DiscoveryClient;
import org.gcube.resources.discovery.client.queries.api.SimpleQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Configuration {
	
	private String scope;
	private String[] server;
	private String username;
	private String password;
	private String backendType;
	private ArrayList<String> dtsHosts;
	private boolean activeDTSFilter;
	private static final String STORAGE_SE_CATEGORY="DataStorage";
	private static final String STORAGE_SE_NAME="StorageManager";
	private static final String SYSTEM_SE_CATEGORY="SystemService";
	private static final String SYSTEM_SE_NAME="storage-manager-trigger";
	private static final String ACCOUNTING_USERNAME="accounting_user";
	private static final String ACCOUNTING_PASSWORDNAME="accounting_pwd";
	private static final String AUTH_SE_CATEGORY="Auth";
	private static final String AUTH_SE_NAME="IAM";
	private static final Object AUTH_SE_AP_NAME = "d4science-oidc-token";
	protected String clientId;
	protected String secret;

	Logger logger= LoggerFactory.getLogger(Configuration.class);
	
	public Configuration(String scope, String user, String password, boolean dtsFilter){
		this.activeDTSFilter=dtsFilter;
		this.scope=scope;
		if(!ValidationUtils.validationScope(scope))
			throw new IllegalArgumentException("invalid scope exception: "+scope);
		
	}
	
	public Configuration(String scope, boolean dtsFilter){
		this.activeDTSFilter=dtsFilter;
		this.scope=scope;
		if(!ValidationUtils.validationScope(scope))
			throw new IllegalArgumentException("invalid scope exception: "+scope);
		ScopeProvider.instance.set(scope);
		
	}
	
	public String[] getServerAccess(List<ServiceEndpoint> resources){
		String savedScope=null;
		if(scope!=null){
			savedScope=ScopeProvider.instance.get();
			ScopeProvider.instance.set(scope);
		}
		logger.debug("get server from IS ");
		getServerRRFws(resources);
		if(scope!=null){
			ScopeProvider.instance.set(savedScope);
		}
		logger.info("server found {} ", server);
		return server;
	}
	
	public String[] getServerRRFws(List<ServiceEndpoint> resources){
//		List<ServiceEndpoint> resources = getStorageServiceEndpoint();
		if(resources.size() > 1){
			logger.info("found "+resources.size()+" RR ");
			// take the RR with property priority setted to DEFAULT
			//take servers take RR name
			return getServers(resources);
		}else if(resources.size() == 1){
			logger.info("found only one RR, take it"); 
			return getServers(resources.get(0));
			//take RR name
		}else{
			logger.error("RUNTIME RESOURCE NOT FOUND IN THIS SCOPE "+ScopeProvider.instance.get());
			throw new RuntimeException("RUNTIME RESOURCE NOT FOUND IN SCOPE: "+ScopeProvider.instance.get());
		}
	}
	
	/**
	 * The accounting user is retrieved from the first serviceEndpoint found in the scope and the first accessPoint inside the serviceEndpoint
	 * @param resources the serviceEndpoint list
	 * @return accounting username
	 */
	protected String getAccountingUser(List<ServiceEndpoint> resources){
		logger.trace("retrieving access point");
		for (AccessPoint ap:resources.get(0).profile().accessPoints()) {
			Map<String, Property>map= ap.propertyMap();
			Property user=map.get(ACCOUNTING_USERNAME);
			if (user!=null){
				logger.debug("accounting user found on SE");
				return user.value();				
			}
		}
		return null;
	}
	
	
	protected void setSecrets(ServiceEndpoint se) throws Exception {
		for (AccessPoint ap:se.profile().accessPoints()) {
			setClientId(ap.username());
			setSecret(StringEncrypter.getEncrypter().decrypt(ap.password()));
		}
	}

	/**
	 * The accounting password is retrieved from the first serviceEndpoint found in the scope and the first accessPoint inside the serviceEndpoint
	 * @param resources the serviceEndpoint list
	 * @return accounting password
	 */
	protected String getAccountingPassword(List<ServiceEndpoint> resources){
		for (AccessPoint ap:resources.get(0).profile().accessPoints()) {
			Map<String, Property>map= ap.propertyMap();
			Property pwd=map.get(ACCOUNTING_PASSWORDNAME);
			if (pwd!=null){
				logger.debug("password field found on SE");
				try {
					return StringEncrypter.getEncrypter().decrypt(pwd.value());
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		return null;
	}

	protected List<ServiceEndpoint> getStorageServiceEndpoint() {
		logger.debug("query for serviceEndpoint ongoing...");
		SimpleQuery query = queryFor(ServiceEndpoint.class);
		query.addCondition("$resource/Profile/Category/text() eq '"+STORAGE_SE_CATEGORY+"' and $resource/Profile/Name eq '"+STORAGE_SE_NAME+"' ");
		DiscoveryClient<ServiceEndpoint> client = clientFor(ServiceEndpoint.class);
		List<ServiceEndpoint> resources = client.submit(query);
		if (resources.size() > 0)
			logger.debug("resource found on IS");
		return resources;
	}
	
	protected ServiceEndpoint getSystemServiceEndpoint() {
		logger.debug("query for serviceEndpoint ongoing...");
		SimpleQuery query = queryFor(ServiceEndpoint.class);
		query.addCondition("$resource/Profile/Category/text() eq '"+SYSTEM_SE_CATEGORY+"' and $resource/Profile/Name eq '"+SYSTEM_SE_NAME+"' ");
		DiscoveryClient<ServiceEndpoint> client = clientFor(ServiceEndpoint.class);
		List<ServiceEndpoint> resources = client.submit(query);
		if (resources.size() > 0)
			return resources.get(0);
		else
			throw new RuntimeException("System ServiceEndpoint not found");
	}

	private String[] getServers(ServiceEndpoint res) {
		server=new String[res.profile().accessPoints().size()];
		int i=0;
		for (AccessPoint ap:res.profile().accessPoints()) {
			if (ap.name().equals("server"+(i+1))) {
				server[i] = ap.address();
	// if presents, try to get user and password			
				username = ap.username();	
				if(username != null && username.length() > 0){
					try {
						password = StringEncrypter.getEncrypter().decrypt(ap.password());
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				i++;
			}
		}
		Iterator<AccessPoint> it= res.profile().accessPoints().iterator();
		AccessPoint ap=(AccessPoint)it.next();
		Map<String, Property>map= ap.propertyMap();
		Property type=map.get("type");
		backendType=type.value();
		logger.debug("Type of backend found "+backendType);
		return server;
	}
	
	private String[] getServers(List<ServiceEndpoint> resources) {
		ServiceEndpoint defaultResource=null;
		logger.debug("search RR with priority ");
	// search RR with property DEFAULT	
		for (ServiceEndpoint res : resources){
			String priority=retrievePropertyValue(res, "priority");
			if (priority!=null){
				defaultResource=res;
				logger.debug("found a RR with priority: ");
				break;
			}
		}
		if(defaultResource!=null){
			server=new String[defaultResource.profile().accessPoints().size()];
			int i=0;
			for (AccessPoint ap:defaultResource.profile().accessPoints()) {
				if (ap.name().equals("server"+(i+1))) {
					server[i] = ap.address();
					// if presents, try to get user and password			
					username = ap.username();
					password="";
					if(username != null && username.length() > 0){
						try {
							password = StringEncrypter.getEncrypter().decrypt(ap.password());
						} catch (Exception e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
					i++;
				}
			}
			backendType=retrievePropertyValue(defaultResource, "type");
			logger.debug("Type of backend found in RR is "+backendType);
			return server;

		}else{
			throw new IllegalStateException("Runtime Resource found are more than 1 but all without default priority setted");
		}
	}
	
	private String retrievePropertyValue(ServiceEndpoint res, String name) {
		Iterator<AccessPoint> it= res.profile().accessPoints().iterator();
		AccessPoint ap=(AccessPoint)it.next();
		Map<String, Property>map= ap.propertyMap();
		Property type=map.get(name);
		if (type!=null)
			return type.value();
		else
			return null;
	}
	
	public List<String> retrieveDTSHosts(){
		if(activeDTSFilter){
			ArrayList<String> scopes=ValidationUtils.getVOScopes(scope);
			dtsHosts= new ArrayList<String>();
			for(String currentScope:scopes){
				String host=getHosts("DataTransformation", "DataTransformationService", currentScope);
				logger.debug("host found: "+host+ " in scope: "+currentScope);
				if(host!=null)
					dtsHosts.add(host);

			}
			for(String host : dtsHosts){
				logger.debug("DTS host: "+host);
			}
			return dtsHosts;
		}else return null;
	}
	
	public String getHosts(String serviceClass, String serviceName) {
		return getHosts(serviceClass, serviceName, scope);
	}
	
	public String getHosts(String serviceClass, String serviceName, String scope) {
		String host=null;
		String currentScope=ScopeProvider.instance.get();
		ScopeProvider.instance.set(scope);
		SimpleQuery query = queryFor(GCoreEndpoint.class);
		query.addCondition("$resource/Profile/ServiceClass eq '"+serviceClass+"' and $resource/Profile/ServiceName eq '"+serviceName+"' ");
		DiscoveryClient<GCoreEndpoint> client = clientFor(GCoreEndpoint.class);
		try{
			List<GCoreEndpoint> resources = client.submit(query);
			if(resources.size()>0){
				GCoreEndpoint res=resources.get(0);
				Iterator<Endpoint> it=res.profile().endpoints().iterator();
				if(it.hasNext()){
					Endpoint endpoint=it.next();
					host=endpoint.uri().toString();
					if(host.contains("//")){
						int begin=host.indexOf("//");
						host=host.substring(begin+2);
						logger.debug("phase#1 "+ host );
						String [] uris=host.split(":");
						logger.debug("phase#2 "+uris[0]);
						host=uris[0];
					}
				}
				ScopeProvider.instance.set(currentScope);
			}
		}catch(Exception e){
			logger.error("FAIL to retrieve resource from scope "+scope+" cause: "+e.getMessage());
			ScopeProvider.instance.set(currentScope);
		}

		return host;
	}
	
	public String getScope() {
		return scope;
	}

	public void setScope(String scope) {
		this.scope = scope;
	}

	public String[] getServer() {
		return server;
	}

	public void setServer(String[] server) {
		this.server = server;
	}

	public String getBackendType() {
		return backendType;
	}

	public void setBackendType(String backendType) {
		this.backendType = backendType;
	}

	public ArrayList<String> getDtsHosts() {
		return dtsHosts;
	}

	public void setDtsHosts(ArrayList<String> dtsHosts) {
		this.dtsHosts = dtsHosts;
	}

	public boolean isActiveDTSFilter() {
		return activeDTSFilter;
	}

	public void setActiveDTSFilter(boolean activeDTSFilter) {
		this.activeDTSFilter = activeDTSFilter;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}
	
	public String getClientId() {
		return clientId;
	}

	public void setClientId(String clientId) {
		logger.debug("clientID "+clientId);
		this.clientId = clientId;
	}

	public String getSecret() {
		return secret;
	}

	public void setSecret(String secret) {
		this.secret = secret;
	}

	String getOidcEndpoint() {
		logger.debug("query for Auth serviceEndpoint ongoing...");
		SimpleQuery query = queryFor(ServiceEndpoint.class);
		query.addCondition("$resource/Profile/Category/text() eq '"+AUTH_SE_CATEGORY+"' and $resource/Profile/Name eq '"+AUTH_SE_NAME+"' ");
		DiscoveryClient<ServiceEndpoint> client = clientFor(ServiceEndpoint.class);
		List<ServiceEndpoint> resources = client.submit(query);
		if (resources.size() > 0) {
			logger.debug("resource found on IS");
			for (AccessPoint ap:resources.get(0).profile().accessPoints()) {
				if (ap.name().equals(AUTH_SE_AP_NAME)) {
					return ap.address();
				}
			}
		}
		throw new RuntimeException("accessPoint "+AUTH_SE_AP_NAME+" not found on SE "+AUTH_SE_CATEGORY+" - "+AUTH_SE_NAME);
	}



}
