package app.mealsmadeeasy.api.auth;

import app.mealsmadeeasy.api.PostgresTestsExtension;
import app.mealsmadeeasy.api.user.User;
import app.mealsmadeeasy.api.user.UserCreateException;
import app.mealsmadeeasy.api.user.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.Cookie;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;

import java.util.Map;
import java.util.UUID;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
@ExtendWith(PostgresTestsExtension.class)
public class AuthControllerTests {

    private static final String TEST_PASSWORD = "test";

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private UserService userService;

    private User seedUser() {
        final String uuid = UUID.randomUUID().toString();
        try {
            return this.userService.createUser(uuid, uuid + "@test.com", TEST_PASSWORD);
        } catch (UserCreateException e) {
            throw new RuntimeException(e);
        }
    }

    private MockHttpServletRequestBuilder getLoginRequest(String username, String password) throws Exception {
        final Map<String, Object> body = Map.of(
                "username", username,
                "password", password
        );
        return post("/auth/login")
                .content(this.objectMapper.writeValueAsString(body))
                .contentType(MediaType.APPLICATION_JSON)
                .with(user(username).password(password));
    }

    @Test
    public void simpleLogin() throws Exception {
        final User user = this.seedUser();
        this.mockMvc.perform(this.getLoginRequest(user.getUsername(), TEST_PASSWORD))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.username").value(user.getUsername()))
                .andExpect(jsonPath("$.accessToken").isString())
                .andExpect(cookie().exists("refresh-token"));
    }

    private Cookie getRefreshTokenCookie(String username, String password) throws Exception {
        final MvcResult loginResult = this.mockMvc.perform(this.getLoginRequest(username, password)).andReturn();
        final Cookie refreshTokenCookie = loginResult.getResponse().getCookie("refresh-token");
        if (refreshTokenCookie == null) {
            throw new NullPointerException("refreshTokenCookie is null");
        }
        return refreshTokenCookie;
    }

    @Test
    public void simpleLogout() throws Exception {
        final User user = this.seedUser();
        final MockHttpServletRequestBuilder req = post("/auth/logout")
                .cookie(this.getRefreshTokenCookie(user.getUsername(), TEST_PASSWORD));
        this.mockMvc.perform(req)
                .andExpect(status().isOk())
                .andExpect(cookie().maxAge("refresh-token", 0));
    }

    @Test
    public void simpleRefresh() throws Exception {
        final User user = this.seedUser();
        final Cookie firstRefreshTokenCookie = this.getRefreshTokenCookie(user.getUsername(), TEST_PASSWORD);
        final MockHttpServletRequestBuilder req = post("/auth/refresh")
                .cookie(firstRefreshTokenCookie);
        final MvcResult res = this.mockMvc.perform(req)
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.username").value(user.getUsername()))
                .andExpect(jsonPath("$.accessToken").isString())
                .andExpect(cookie().exists("refresh-token"))
                .andReturn();
        final Cookie secondRefreshTokenCookie = res.getResponse().getCookie("refresh-token");
        if (secondRefreshTokenCookie == null) {
            throw new NullPointerException("secondRefreshTokenCookie is null");
        }
        assertThat(firstRefreshTokenCookie.getValue(), is(not(secondRefreshTokenCookie.getValue())));
    }

}
