package org.ros.chatto.security;

import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.ros.chatto.logged.TokenCacheUtil;
import org.ros.chatto.model.UserToken;
import org.ros.chatto.service.UserTokenService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.token.Token;
import org.springframework.security.core.token.TokenService;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

@Component
public class TokenAuthenticationFilter extends OncePerRequestFilter {

	@Autowired
	private final UserTokenService userTokenService;

	@Autowired
	private final TokenService tokenService;

	private final Logger logger = LoggerFactory
			.getLogger(TokenAuthenticationFilter.class);

	private final int tokenTimeoutDuration;

	public TokenAuthenticationFilter(UserTokenService userTokenService,
			TokenService tokenService,
			@Value("${chatto.token.timeout-duration}") String tokenTimeoutDuration) {
		this.tokenTimeoutDuration = Integer.parseInt(tokenTimeoutDuration);
		this.userTokenService = userTokenService;
		this.tokenService = tokenService;
	}

	private boolean isTokenExpired(UserToken userToken) {
		Duration duration = Duration.between(userToken.getCreationTime(),
				Instant.now());
		long minutes = Math.abs(duration.toMinutes());
		if (minutes > tokenTimeoutDuration) {
			return true;
		}
		return false;
	}

	@Override
	protected void doFilterInternal(HttpServletRequest request,
			HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		try {
			final String accessToken = request.getHeader("X-AUTH-TOKEN");
			if (null != accessToken) {
				// get and check whether token is valid ( from DB or file
				// wherever you are
				// storing the token)
				Token token = tokenService.verifyToken(accessToken);

				if (token == null) {
					throw new BadCredentialsException("Token not issued by us");
				}
				UserToken userToken = userTokenService
						.getTokenByTokenString(accessToken);

				if (userToken == null) {
					throw new BadCredentialsException("Token not found");
				}

				String userName = userToken.getUserName();
				if (userName == null) {
					throw new BadCredentialsException("User not found");
				}

				boolean isTokenExpired = isTokenExpired(userToken);
				logger.trace(String.format("Token for %s is expired? %s",
						userName, isTokenExpired));

				if (!isTokenExpired) {
					userToken.setCreationTime(Instant.now());
					userTokenService.saveToken(userToken);
					SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(
							userToken.getRole());
					var updatedAuthorities = new ArrayList<SimpleGrantedAuthority>();
					updatedAuthorities.add(simpleGrantedAuthority);
					final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
							userName, token.getKey(), updatedAuthorities);
					SecurityContextHolder.getContext()
							.setAuthentication(authentication);
				} else {
					userTokenService.deleteToken(userToken);
					TokenCacheUtil
							.evictSingleTokenValue(userToken.getTokenContent());
					// response.setHeader(HttpHeaders.CONTENT_TYPE,
					// MediaType.TEXT_PLAIN_VALUE);
					// response.setStatus(440);
					// // response.sendError(440, "Token authentication error:
					// Token has expired");
					// response.getWriter().write("Token authentication error:
					// Token has expired");
					// logger.warn("Token authentication error: Token has
					// expired");
					// return;
				}

			}

			filterChain.doFilter(request, response);
		}

		catch (BadCredentialsException e) {
			response.setHeader(HttpHeaders.CONTENT_TYPE,
					MediaType.TEXT_PLAIN_VALUE);
			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			// response.sendError(HttpServletResponse.SC_BAD_REQUEST,
			// e.getMessage());
			response.getWriter().write("Token authentication error");
			logger.warn("Token authentication error: " + e.getMessage());
		}
	}

}