package io.aiven.klaw.clusterapi;

import static io.aiven.klaw.clusterapi.models.enums.ClusterStatus.ONLINE;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.aiven.klaw.clusterapi.models.enums.ClusterStatus;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.security.Key;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.UUID;
import javax.crypto.spec.SecretKeySpec;
import org.apache.tomcat.util.codec.binary.Base64;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

@SpringBootTest(
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
    classes = KafkaClusterApiApplication.class)
@AutoConfigureMockMvc
@TestPropertySource(locations = "classpath:application.properties")
@TestMethodOrder(OrderAnnotation.class)
@DirtiesContext
public class AuthenticationIntegrationIT {

  public static final String KWCLUSTERAPIUSER = "kwclusterapiuser";
  public static final String AUTHORIZATION = "Authorization";
  public static final String BEARER_PREFIX = "Bearer ";
  public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

  @Value("${klaw.clusterapi.access.base64.secret}")
  private String clusterAccessSecret;

  @Autowired private MockMvc mvc;

  @Test
  @Order(1)
  public void getApiStatus_Success() throws Exception {
    MockHttpServletResponse response =
        mvc.perform(
                MockMvcRequestBuilders.get("/topics/getApiStatus")
                    .contentType(MediaType.APPLICATION_JSON)
                    .header(
                        AUTHORIZATION,
                        BEARER_PREFIX + generateToken(KWCLUSTERAPIUSER, clusterAccessSecret, 3L))
                    .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andReturn()
            .getResponse();

    ClusterStatus clusterStatus =
        OBJECT_MAPPER.readValue(response.getContentAsString(), ClusterStatus.class);
    assertThat(clusterStatus).isEqualTo(ONLINE);
  }

  @Test
  @Order(2)
  public void getApiStatus_InValidSubjectRequest() {
    RuntimeException thrown =
        Assertions.assertThrows(
            RuntimeException.class,
            () ->
                mvc.perform(
                        MockMvcRequestBuilders.get("/topics/getApiStatus")
                            .contentType(MediaType.APPLICATION_JSON)
                            .header(
                                AUTHORIZATION,
                                BEARER_PREFIX
                                    + generateToken("notExistingUser", clusterAccessSecret, 3L))
                            .accept(MediaType.APPLICATION_JSON))
                    .andExpect(status().isUnauthorized())
                    .andReturn()
                    .getResponse());
    assertThat(thrown.getMessage()).isEqualTo("User/subject not found !!");
  }

  @Test
  @Order(3)
  public void getApiStatus_InValidSecretRequest() throws Exception {
    String invalidSecret = "this is an invalid secret for the test";
    String base64EncodedSecret = Base64.encodeBase64String(invalidSecret.getBytes());
    mvc.perform(
            MockMvcRequestBuilders.get("/topics/getApiStatus")
                .contentType(MediaType.APPLICATION_JSON)
                .header(
                    AUTHORIZATION,
                    BEARER_PREFIX + generateToken(KWCLUSTERAPIUSER, base64EncodedSecret, 3L))
                .accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isUnauthorized())
        .andReturn()
        .getResponse();
  }

  @Test
  @Order(4)
  public void getApiStatus_ExpiredToken() throws Exception {
    mvc.perform(
            MockMvcRequestBuilders.get("/topics/getApiStatus")
                .contentType(MediaType.APPLICATION_JSON)
                .header(
                    AUTHORIZATION,
                    BEARER_PREFIX + generateToken(KWCLUSTERAPIUSER, clusterAccessSecret, -3L))
                .accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isUnauthorized())
        .andReturn()
        .getResponse();
  }

  private String generateToken(
      String clusterApiUser, String clusterAccessSecret, long expirationTime) {
    Key hmacKey =
        new SecretKeySpec(
            Base64.decodeBase64(clusterAccessSecret), SignatureAlgorithm.HS256.getJcaName());
    Instant now = Instant.now();

    return Jwts.builder()
        .claim("name", clusterApiUser)
        .subject(clusterApiUser)
        .id(UUID.randomUUID().toString())
        .issuedAt(Date.from(now))
        .expiration(Date.from(now.plus(expirationTime, ChronoUnit.MINUTES)))
        .signWith(hmacKey)
        .compact();
  }
}
