package eu.dnetlib.functionality.modular.ui.users;

import java.net.URLDecoder;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.KeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import com.google.gson.Gson;

public class SimpleSSOAuthorizationManager implements AuthorizationManager {

	private static final Log log = LogFactory.getLog(SimpleSSOAuthorizationManager.class);
		
	private Resource pubKeyFile = new ClassPathResource("/eu/dnetlib/functionality/modular/ui/users/pubkey.der");
	private String pubKeyAlgo = "RSA";
	private String signatureAlgo = "SHA1withRSA";
	private PublicKey publicKey;
	private AuthorizationDAO authorizationDAO;
	private String defaultSuperAdmin;

	@Override
	public User obtainUserDetails(final HttpServletRequest request) {

		if (request.getCookies() != null) {
			for (Cookie cookie : request.getCookies()) {
				if (cookie.getName().equalsIgnoreCase("rinfra-user")) {
					try {
						return processCookie(cookie.getValue());
					} catch (Exception e) {
						log.error("Error processing cookie: " + cookie.getValue(), e);
						return null;
					}
				}
			}
		}
		return null;
	}

	private User processCookie(final String s) throws Exception {
		if (s == null || s.isEmpty()) {
			return null;
		}
		final Gson gson = new Gson();

		final Map<?,?> map = gson.fromJson(new String(Base64.decodeBase64(URLDecoder.decode(s.trim(), "UTF-8"))), Map.class);
		final String message = (String) map.get("payload");
		final String signature = (String) map.get("signature");
		if (isValidMessage(message, signature)) {
			final Map<?,?> userMap = gson.fromJson(message, Map.class);
			if (userMap.containsKey("uid")) {
				final String uid = (String) userMap.get("uid");
				final User user = new User(uid);
				user.setEmail((String) userMap.get("email"));
				user.setFullname(userMap.containsKey("fullname") ? (String) userMap.get("fullname") : uid);
				user.setPermissionLevels(authorizationDAO.getPermissionLevels(uid));
				
				if (isDefaultSuperAdmin(uid)) {
					user.getPermissionLevels().add(PermissionLevel.SUPER_ADMIN);
				}
				return user;
			}
		}
		return null;
	}

	private boolean isDefaultSuperAdmin(final String uid) {
		return (uid != null && !uid.isEmpty() && uid.equals(getDefaultSuperAdmin()));
	}

	protected boolean isValidMessage(final String message, final String signature) {
		log.info("checking message " + message + " with sig " + signature);
		if (message == null || message.isEmpty() || signature == null || signature.isEmpty()) {
			log.error("Null  or empty values in message or signature");
			return false;
		}
		
		try {
			final Signature sg = Signature.getInstance(getSignatureAlgo());
			sg.initVerify(getPublicKey());
			sg.update(message.getBytes());
			return sg.verify(Hex.decodeHex(signature.toCharArray()));
		} catch(Exception e) {
			log.error("Error verifyng signature", e);
			return false;
		}
	}

	public void init() throws Exception {
		final byte[] keyBytes = IOUtils.toByteArray(getPubKeyFile().getInputStream());
		final KeySpec spec = new X509EncodedKeySpec(keyBytes);
		
		setPublicKey(KeyFactory.getInstance(getPubKeyAlgo()).generatePublic(spec));
	}

	public Resource getPubKeyFile() {
		return pubKeyFile;
	}
	
	@Required
	public void setPubKeyFile(Resource pubKeyFile) {
		this.pubKeyFile = pubKeyFile;
	}

	public String getPubKeyAlgo() {
		return pubKeyAlgo;
	}

	@Required
	public void setPubKeyAlgo(String pubKeyAlgo) {
		this.pubKeyAlgo = pubKeyAlgo;
	}

	public String getSignatureAlgo() {
		return signatureAlgo;
	}

	@Required
	public void setSignatureAlgo(String signatureAlgo) {
		this.signatureAlgo = signatureAlgo;
	}

	public PublicKey getPublicKey() {
		return publicKey;
	}

	public void setPublicKey(PublicKey publicKey) {
		this.publicKey = publicKey;
	}

	public AuthorizationDAO getAuthorizationDAO() {
		return authorizationDAO;
	}

	@Required
	public void setAuthorizationDAO(AuthorizationDAO authorizationDAO) {
		this.authorizationDAO = authorizationDAO;
	}

	public String getDefaultSuperAdmin() {
		return defaultSuperAdmin;
	}
	
	@Required
	public void setDefaultSuperAdmin(String defaultSuperAdmin) {
		this.defaultSuperAdmin = defaultSuperAdmin;
	}

}
