package com.codebyte.api.auth;

import static org.assertj.core.api.Assertions.assertThat;
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;

import com.codebyte.api.auth.repository.UserRepository;
import com.codebyte.api.support.AbstractPostgresIT;
import com.icegreen.greenmail.junit5.GreenMailExtension;
import com.icegreen.greenmail.util.GreenMailUtil;
import com.icegreen.greenmail.util.ServerSetupTest;
import jakarta.mail.internet.MimeMessage;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
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.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;

/**
 * End-to-end registration + activation over HTTP with real Postgres and a real SMTP (GreenMail).
 */
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
class AuthRegistrationIT extends AbstractPostgresIT {

    private static final Pattern TOKEN_PATTERN = Pattern.compile("activation token is: (\\S+)");

    @RegisterExtension
    static final GreenMailExtension GREENMAIL =
            new GreenMailExtension(ServerSetupTest.SMTP).withPerMethodLifecycle(true);

    @Autowired private MockMvc mockMvc;
    @Autowired private UserRepository userRepository;

    @DynamicPropertySource
    static void mailProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.mail.host", () -> "127.0.0.1");
        registry.add("spring.mail.port", () -> ServerSetupTest.SMTP.getPort());
    }

    private void register(String email, String password) throws Exception {
        String body = "{\"email\":\"%s\",\"password\":\"%s\"}".formatted(email, password);
        mockMvc.perform(
                        post("/api/v1/auth/register")
                                .contentType(MediaType.APPLICATION_JSON)
                                .content(body))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.message").exists());
    }

    @Test
    void should_register_send_activation_email_then_activate() throws Exception {
        String email = "alice@example.com";
        register(email, "correct horse battery");

        assertThat(GREENMAIL.waitForIncomingEmail(5000, 1)).isTrue();
        MimeMessage[] messages = GREENMAIL.getReceivedMessages();
        assertThat(messages).hasSize(1);
        assertThat(messages[0].getAllRecipients()[0].toString()).isEqualTo(email);

        Matcher matcher = TOKEN_PATTERN.matcher(GreenMailUtil.getBody(messages[0]));
        assertThat(matcher.find()).isTrue();
        String token = matcher.group(1);

        mockMvc.perform(
                        post("/api/v1/auth/activate")
                                .contentType(MediaType.APPLICATION_JSON)
                                .content("{\"token\":\"%s\"}".formatted(token)))
                .andExpect(status().isOk());

        var user = userRepository.findByEmailIgnoreCase(email).orElseThrow();
        assertThat(user.isActivatedAccount()).isTrue();
        assertThat(user.getActivatedAt()).isNotNull();
    }

    @Test
    void should_not_reveal_whether_email_exists_on_duplicate_registration() throws Exception {
        String email = "bob@example.com";
        register(email, "correct horse battery");
        // Second registration with the same email must look identical and create nothing new.
        register(email, "another valid passphrase");

        assertThat(userRepository.findAll()).hasSize(1);
        // Only the first registration sent an email; the duplicate sent none.
        assertThat(GREENMAIL.getReceivedMessages()).hasSize(1);
    }

    @Test
    void should_reject_registration_with_weak_password() throws Exception {
        mockMvc.perform(
                        post("/api/v1/auth/register")
                                .contentType(MediaType.APPLICATION_JSON)
                                .content("{\"email\":\"weak@example.com\",\"password\":\"short\"}"))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code", org.hamcrest.Matchers.is("VALIDATION_FAILED")));
    }

    @Test
    void should_reject_invalid_activation_token() throws Exception {
        mockMvc.perform(
                        post("/api/v1/auth/activate")
                                .contentType(MediaType.APPLICATION_JSON)
                                .content("{\"token\":\"definitely-not-valid\"}"))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code", org.hamcrest.Matchers.is("TOKEN_INVALID")));
    }
}
