package com.walkguide.integration;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.walkguide.dto.request.LoginRequest;
import com.walkguide.dto.request.RegisterRequest;
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.ActiveProfiles;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
 * Base class untuk semua Integration Test menggunakan Testcontainers.
 * Spin up PostgreSQL nyata dalam Docker container, jalankan Flyway migration,
 * lalu test real HTTP request via MockMvc terhadap Spring context penuh.
 */
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@Testcontainers
@ActiveProfiles("test")
public abstract class AbstractIntegrationTest {

    // =========================================================================
    // SHARED POSTGRESQL TESTCONTAINER — satu instance untuk semua test class
    // =========================================================================

    @Container
    static final PostgreSQLContainer<?> postgres =
            new PostgreSQLContainer<>("postgres:15-alpine")
                    .withDatabaseName("walkguide_test")
                    .withUsername("testuser")
                    .withPassword("testpass");

    /**
     * Override spring datasource properties menggunakan URL dari Testcontainer.
     * Menggantikan koneksi ke server kampus saat test berjalan.
     */
    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
        registry.add("spring.flyway.enabled", () -> "true");
        registry.add("spring.jpa.hibernate.ddl-auto", () -> "validate");
        registry.add("firebase.enabled", () -> "false");
    }

    // =========================================================================
    // AUTOWIRED FIELDS
    // =========================================================================

    @Autowired
    protected MockMvc mockMvc;

    @Autowired
    protected ObjectMapper objectMapper;

    // =========================================================================
    // HELPER METHODS
    // =========================================================================

    /**
     * Register user baru dan return access token.
     * DTO RegisterRequest pakai @Data (Lombok) — setter tersedia saat runtime Maven,
     * meskipun NetBeans IDE menampilkan false-positive error karena Lombok-NetBeans incompatibility.
     */
    protected String registerAndGetToken(String email, String password, String role)
            throws Exception {
        RegisterRequest req = new RegisterRequest();
        req.setEmail(email);
        req.setPassword(password);
        req.setDisplayName("Test " + role);
        req.setRole(role);

        MvcResult result = mockMvc.perform(
                post("/api/v1/auth/register")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(req)))
                .andExpect(status().isOk())
                .andReturn();

        JsonNode json = objectMapper.readTree(result.getResponse().getContentAsString());
        return json.path("data").path("accessToken").asText();
    }

    /**
     * Login dengan credentials dan return access token.
     */
    protected String loginAndGetToken(String email, String password) throws Exception {
        LoginRequest req = new LoginRequest();
        req.setEmail(email);
        req.setPassword(password);

        MvcResult result = mockMvc.perform(
                post("/api/v1/auth/login")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(req)))
                .andExpect(status().isOk())
                .andReturn();

        JsonNode json = objectMapper.readTree(result.getResponse().getContentAsString());
        return json.path("data").path("accessToken").asText();
    }

    /**
     * Register user dan return seluruh AuthDataResponse sebagai JsonNode.
     * Berguna ketika test butuh uniqueUserId, userId, role, dll.
     */
    protected JsonNode registerAndGetFullResponse(String email, String password, String role)
            throws Exception {
        RegisterRequest req = new RegisterRequest();
        req.setEmail(email);
        req.setPassword(password);
        req.setDisplayName("Test " + role);
        req.setRole(role);

        MvcResult result = mockMvc.perform(
                post("/api/v1/auth/register")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(req)))
                .andExpect(status().isOk())
                .andReturn();

        JsonNode json = objectMapper.readTree(result.getResponse().getContentAsString());
        return json.path("data");
    }

    /** Buat Authorization header dari token. */
    protected String bearerToken(String token) {
        return "Bearer " + token;
    }
}