package app.mealsmadeeasy.api.signup;

import app.mealsmadeeasy.api.PostgresTestsExtension;
import app.mealsmadeeasy.api.user.UserCreateException.Type;
import app.mealsmadeeasy.api.user.UserService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
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.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

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

import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

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

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private UserService userService;

    private MockHttpServletRequestBuilder getCheckUsernameRequest(String usernameToCheck)
            throws JsonProcessingException {
        final Map<String, Object> body = Map.of("username", usernameToCheck);
        return MockMvcRequestBuilders.get("/sign-up/check-username")
                .content(this.objectMapper.writeValueAsString(body))
                .contentType(MediaType.APPLICATION_JSON);
    }

    @Test
    public void checkUsernameExpectAvailable() throws Exception {
        final String username = UUID.randomUUID().toString();
        this.mockMvc.perform(this.getCheckUsernameRequest(username))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.isAvailable").value(true));
    }

    @Test
    public void checkUsernameExpectNotAvailable() throws Exception {
        final String username = UUID.randomUUID().toString();
        this.userService.createUser(username, username + "@notavailable.com", "test");
        this.mockMvc.perform(this.getCheckUsernameRequest(username))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.isAvailable").value(false));
    }


    private MockHttpServletRequestBuilder getCheckEmailRequest(String emailToCheck) throws JsonProcessingException {
        final Map<String, Object> body = Map.of("email", emailToCheck);
        return MockMvcRequestBuilders.get("/sign-up/check-email")
                .content(this.objectMapper.writeValueAsString(body))
                .contentType(MediaType.APPLICATION_JSON);
    }

    @Test
    public void checkEmailExpectAvailable() throws Exception {
        this.mockMvc.perform(this.getCheckEmailRequest(UUID.randomUUID() + "@available.com"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.isAvailable").value(true));
    }

    @Test
    public void checkEmailExpectNotAvailable() throws Exception {
        final String notAvailable = UUID.randomUUID().toString();
        this.userService.createUser(notAvailable, notAvailable + "@notavailable.com", "test");
        this.mockMvc.perform(this.getCheckEmailRequest(notAvailable + "@notavailable.com"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.isAvailable").value(false));
    }

    @Test
    public void simpleSignUp() throws Exception {
        final SignUpBody body = new SignUpBody();
        final String username = UUID.randomUUID().toString();
        body.setUsername(username);
        body.setEmail(username + "@user.com");
        body.setPassword("test");
        final MockHttpServletRequestBuilder req = post("/sign-up")
                .content(this.objectMapper.writeValueAsString(body))
                .contentType(MediaType.APPLICATION_JSON);
        this.mockMvc.perform(req)
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.username").value(username));
    }

    @Test
    public void signUpBadRequestWhenUsernameTaken() throws Exception {
        final String takenUsername = UUID.randomUUID().toString();
        this.userService.createUser(takenUsername, takenUsername + "@taken.com", "test");
        final SignUpBody body = new SignUpBody();
        body.setUsername(takenUsername);
        body.setEmail(UUID.randomUUID() + "@taken.com"); // n.b.: not taken email
        body.setPassword("test");
        final MockHttpServletRequestBuilder req = post("/sign-up")
                .content(this.objectMapper.writeValueAsString(body))
                .contentType(MediaType.APPLICATION_JSON);
        this.mockMvc.perform(req)
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.error.type").value(Type.USERNAME_TAKEN.toString()))
                .andExpect(jsonPath("$.error.message").value(containsString(takenUsername)));
    }

    @Test
    public void signUpBadRequestWhenEmailTaken() throws Exception {
        final String takenEmail = UUID.randomUUID() + "@taken.com";
        this.userService.createUser(UUID.randomUUID().toString(), takenEmail, "test");
        final SignUpBody body = new SignUpBody();
        body.setUsername(UUID.randomUUID().toString()); // n.b.: random username
        body.setEmail(takenEmail);
        body.setPassword("test");
        final MockHttpServletRequestBuilder req = post("/sign-up")
                .content(this.objectMapper.writeValueAsString(body))
                .contentType(MediaType.APPLICATION_JSON);
        this.mockMvc.perform(req)
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.error.type").value(Type.EMAIL_TAKEN.toString()))
                .andExpect(jsonPath("$.error.message").value(containsString(takenEmail)));
    }

}
