package eu.eudat.logic.handlers;

import eu.eudat.exceptions.security.UnauthorisedException;
import eu.eudat.logic.security.claims.ClaimedAuthorities;
import eu.eudat.logic.services.operations.authentication.AuthenticationService;
import eu.eudat.models.data.security.Principal;
import eu.eudat.types.Authorities;
import org.apache.catalina.connector.RequestFacade;
import org.apache.tomcat.util.buf.MessageBytes;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import java.lang.annotation.Annotation;
import java.util.*;


public final class PrincipalArgumentResolver implements HandlerMethodArgumentResolver {

	private AuthenticationService verifiedUserAuthenticationService;
	private AuthenticationService nonVerifiedUserAuthenticationService;

	public PrincipalArgumentResolver(AuthenticationService verifiedUserAuthenticationService, AuthenticationService nonVerifiedUserAuthenticationService) {
		this.verifiedUserAuthenticationService = verifiedUserAuthenticationService;
		this.nonVerifiedUserAuthenticationService = nonVerifiedUserAuthenticationService;
	}

	@Override
	public boolean supportsParameter(MethodParameter methodParameter) {
		return methodParameter.getParameterType().equals(Principal.class);
	}

	@Override
	public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
		String token = nativeWebRequest.getHeader("AuthToken");

		boolean checkMailNull = ((ServletWebRequest) nativeWebRequest).getRequest().getRequestURI().startsWith("/api/emailConfirmation");
		AuthenticationService authenticationService = checkMailNull ? this.nonVerifiedUserAuthenticationService : this.verifiedUserAuthenticationService;

		Optional<Annotation> claimsAnnotation = Arrays.stream(methodParameter.getParameterAnnotations()).filter(annotation -> annotation.annotationType().equals(ClaimedAuthorities.class)).findAny();
		List<Authorities> claimList = claimsAnnotation.map(annotation -> Arrays.asList(((ClaimedAuthorities) annotation).claims())).orElse(Authorities.all());
		if (claimList.size() == 1 && claimList.get(0).equals(Authorities.ANONYMOUS)) {
			return new Principal();
		} else if (claimList.contains(Authorities.ANONYMOUS) && token == null) {
			return new Principal();
		}

		if (token == null) throw new UnauthorisedException("Authentication Information Is Missing");
		UUID authToken;
		try {
			authToken = UUID.fromString(token);
		} catch (IllegalArgumentException ex) {
			throw new UnauthorisedException("Authentication Information Is Missing");
		}
		Principal principal = authenticationService.Touch(authToken);
		if (principal == null) throw new UnauthorisedException("Authentication Information Missing");
		if (!claimList.contains(Authorities.ANONYMOUS) && !principal.isAuthorized(claimList))
			throw new UnauthorisedException("You are not Authorized For this Action");

		return principal;
	}

	private Date addADay(Date date) {
		Date dt = new Date();
		Calendar c = Calendar.getInstance();
		c.setTime(dt);
		c.add(Calendar.DATE, 1);
		dt = c.getTime();
		return dt;
	}
}