package de.itvsh.goofy;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.Spy;

import com.auth0.jwt.exceptions.JWTVerificationException;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

class JwtTokenUtilTest {

	@Spy
	private JwtTokenUtil jwtTokenUtil;

	private static final String TOKEN_SECRET = "t0pS3cr3t";

	@BeforeEach
	public void initTest() throws Exception {
		Field tokenSecretField = JwtTokenUtil.class.getDeclaredField("secret");
		tokenSecretField.setAccessible(true);
		tokenSecretField.set(jwtTokenUtil, TOKEN_SECRET);
	}

	@Nested
	@DisplayName("Verify token generation")
	class TestGenerateToken {

		private String generatedToken;

		@BeforeEach
		void initTest() {
			generatedToken = jwtTokenUtil.generateToken(SecurityTestFactory.SUBJECT.toString(), SecurityTestFactory.USER_FIRSTNAME,
					SecurityTestFactory.USER_LASTNAME, SecurityTestFactory.AUTHORITIES);
		}

		@Test
		void userId() {
			var userId = getParsedBody().getSubject();

			assertThat(userId).isEqualTo(SecurityTestFactory.SUBJECT.toString());
		}

		@Test
		void expirationDate() {
			var before = new Date();
			var expirationDate = getParsedBody().getExpiration();
			var after = new Date(System.currentTimeMillis() + 900000);

			assertThat(expirationDate).isAfter(before).isBefore(after);
		}

		private Claims getParsedBody() {
			return Jwts.parser().setSigningKey(TOKEN_SECRET.getBytes()).parseClaimsJws(generatedToken).getBody();
		}
	}

	@Nested
	class TestVerifyToken {

		@Test
		void shouldDoNotThrowExcepionOnValidToken() {
			var token = buildToken(UUID.randomUUID().toString(), TOKEN_SECRET, 60000);

			jwtTokenUtil.verifyToken(token);
		}

		@Test
		void shouldThrowExceptionOnInvalidToken() {
			var token = buildToken(UUID.randomUUID().toString(), "invalid_token", 60000);

			assertThrows(JWTVerificationException.class, () -> jwtTokenUtil.verifyToken(token));
		}

		@Test
		void shouldThrowExceptionOnTimeExpired() {
			var token = buildToken(UUID.randomUUID().toString(), TOKEN_SECRET, -1000);

			assertThrows(JWTVerificationException.class, () -> jwtTokenUtil.verifyToken(token));
		}

		private String buildToken(String subject, String token, int expiredTime) {
			Map<String, Object> claims = new HashMap<>();
			claims.put(JwtTokenUtil.FIRSTNAME_CLAIM, SecurityTestFactory.USER_FIRSTNAME);
			claims.put(JwtTokenUtil.LASTNAME_CLAIM, SecurityTestFactory.USER_LASTNAME);
			claims.put(JwtTokenUtil.ROLE_CLAIM, SecurityTestFactory.AUTHORITIES);

			return Jwts.builder()//
					.setClaims(claims)//
					.setSubject(subject)//
					.setHeaderParam("typ", JwtTokenUtil.TOKEN_TYPE)//
					.setIssuer(JwtTokenUtil.TOKEN_ISSUER).setIssuedAt(new Date(System.currentTimeMillis()))//
					.setExpiration(new Date(System.currentTimeMillis() + expiredTime))//
					.setAudience(JwtTokenUtil.TOKEN_AUDIENCE)//
					.signWith(SignatureAlgorithm.HS512, token.getBytes())//
					.compact();
		}
	}
}