package org.bench4q.master.domain.service.auth;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Calendar;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;

import org.bench4q.master.domain.entity.User;
import org.bench4q.master.domain.repository.UserRepository;
import org.bench4q.master.helper.StringHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class AuthenticationManager {
	private final int defaultExpiresTime = 3600;
	private CryptoManager cryptoManager;
	private UserRepository userRepository;
	private StringHelper stringHelper;

	private CryptoManager getCryptoManager() {
		return cryptoManager;
	}

	@Autowired
	private void setCryptoManager(CryptoManager cryptoManager) {
		this.cryptoManager = cryptoManager;
	}

	private StringHelper getStringHelper() {
		return stringHelper;
	}

	@Autowired
	private void setStringHelper(StringHelper stringHelper) {
		this.stringHelper = stringHelper;
	}

	private UserRepository getUserRepository() {
		return userRepository;
	}

	@Autowired
	private void setUserRepository(UserRepository userRepository) {
		this.userRepository = userRepository;
	}

	public User getPrincipal(HttpServletRequest request) {
		AccessToken accessToken = this.getAccessToken(request);
		if (accessToken == null) {
			return null;
		}
		return this.extractUser(accessToken);
	}

	public boolean checkScope(HttpServletRequest request, byte requiredScope) {
		AccessToken accessToken = this.getAccessToken(request);
		if (accessToken == null) {
			return false;
		}
		return isScopeMeet(accessToken.getScope(), requiredScope);
	}

	public boolean isScopeMeet(byte actualScope, byte requiredScope) {
		return actualScope >= requiredScope;
	}

	private String generateCredential(User user) {
		AccessToken accessToken = this.generateAccessToken(user);
		return this.generateCredentialFromAccessToken(accessToken);
	}

	public String generateCredential(String userName) {
		return this.generateCredential(this.getUserRepository().getUser(
				userName));
	}

	private AccessToken getAccessToken(HttpServletRequest request) {
		if (request.getHeader("Authorization") == null) {
			return null;
		}
		if (!request.getHeader("Authorization").startsWith("Bearer ")) {
			return null;
		}
		String hex = request.getHeader("Authorization").substring(
				"Bearer ".length());
		byte[] bytes = this.getStringHelper().convertToBytes(hex);
		return this.extractAccessToken(bytes);
	}

	private User extractUser(AccessToken accessToken) {
		if (accessToken == null) {
			return null;
		}
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(accessToken.getGenerateTime());
		calendar.add(Calendar.SECOND, accessToken.getExpiresIn());
		Date expireTime = calendar.getTime();
		if (expireTime.before(new Date(System.currentTimeMillis()))) {
			return null;
		}
		return this.getUserRepository().getUser(accessToken.getUserName());
	}

	private AccessToken generateAccessToken(User user) {
		AccessToken accessToken = new AccessToken();
		accessToken.setExpiresIn(this.defaultExpiresTime);
		accessToken.setGenerateTime(new Date(System.currentTimeMillis()));
		accessToken.setPassword(user.getPassword());
		accessToken.setUserName(user.getUserName());
		accessToken.setScope(user.getScope());
		return accessToken;
	}

	public AccessToken generateAccessToken(String userName) {
		return this.generateAccessToken(this.getUserRepository().getUser(
				userName));
	}

	private AccessToken extractAccessToken(byte[] bytes) {
		try {
			byte[] decrypted = this.getCryptoManager().decrypt(bytes);
			ObjectInputStream inputStream = new ObjectInputStream(
					new ByteArrayInputStream(decrypted));
			return (AccessToken) inputStream.readObject();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	private String generateCredentialFromAccessToken(AccessToken accessToken) {
		try {
			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
			ObjectOutputStream stream = new ObjectOutputStream(outputStream);
			stream.writeObject(accessToken);
			byte[] data = outputStream.toByteArray();
			byte[] encryptedData = this.getCryptoManager().encrypt(data);
			return this.getStringHelper().convertToHexString(encryptedData);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

}
