package com.walkguide.integration;

import org.junit.jupiter.api.Disabled;

import com.fasterxml.jackson.databind.JsonNode;
import com.walkguide.dto.request.InviteUserRequest;
import com.walkguide.dto.request.PairingResponseRequest;
import org.junit.jupiter.api.*;
import org.springframework.http.MediaType;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

/**
 * Integration Test — Alur Pairing Guardian ↔ User.
 *
 * Menggunakan Testcontainers PostgreSQL nyata.
 * State transitions yang di-test:
 *   Guardian invite User → PENDING → User accept → ACTIVE → unpair
 *   Guardian invite User → PENDING → User reject → REJECTED
 *
 * CATATAN IDE: Error "setUniqueUserId/setPairingId/setAccept" di NetBeans
 * adalah FALSE POSITIVE karena Lombok @Data tidak diproses IDE.
 * Berjalan normal dengan: mvn test
 */
@DisplayName("Integration Test — Pairing Flow (Testcontainers)")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Disabled("Requires Docker/Testcontainers")
class PairingIntegrationTest extends AbstractIntegrationTest {

    private static final String USER_EMAIL     = "pairing_user@walkguide.test";
    private static final String USER_PASS      = "userpass123";
    private static final String GUARDIAN_EMAIL = "pairing_guardian@walkguide.test";
    private static final String GUARDIAN_PASS  = "guardianpass123";

    private static String userToken;
    private static String guardianToken;
    private static String uniqueUserId;
    private static Long   pairingId;

    // ==========================================================================
    // Setup — register kedua user
    // ==========================================================================

    @Test
    @Order(1)
    @DisplayName("Setup — Register User dan Guardian, simpan token + uniqueUserId")
    void setup_registerBothRoles() throws Exception {
        JsonNode userData = registerAndGetFullResponse(USER_EMAIL, USER_PASS, "USER");
        userToken    = userData.path("accessToken").asText();
        uniqueUserId = userData.path("uniqueUserId").asText();

        assertThat(userToken).isNotBlank();
        assertThat(uniqueUserId).hasSize(12);

        JsonNode guardianData = registerAndGetFullResponse(GUARDIAN_EMAIL, GUARDIAN_PASS, "GUARDIAN");
        guardianToken = guardianData.path("accessToken").asText();

        assertThat(guardianToken).isNotBlank();
    }

    // ==========================================================================
    // Invite
    // ==========================================================================

    @Test
    @Order(2)
    @DisplayName("POST /shared/pairing/invite — Guardian invite User dengan uniqueUserId valid → PENDING")
    void invite_validUniqueUserId_returnsPending() throws Exception {
        // InviteUserRequest: field = uniqueUserId (@Data → setter tersedia di runtime)
        InviteUserRequest req = new InviteUserRequest();
        req.setUniqueUserId(uniqueUserId);

        String responseBody = mockMvc.perform(post("/api/v1/shared/pairing/invite")
                        .header("Authorization", bearerToken(guardianToken))
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(req)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.message").value("Undangan dikirim ke user"))
                .andExpect(jsonPath("$.data.status").value("PENDING"))
                .andReturn().getResponse().getContentAsString();

        JsonNode data = objectMapper.readTree(responseBody).path("data");
        pairingId = data.path("pairingId").asLong();
        assertThat(pairingId).isGreaterThan(0);
    }

    @Test
    @Order(3)
    @DisplayName("POST /shared/pairing/invite — uniqueUserId tidak ada → error")
    void invite_unknownUniqueUserId_returnsError() throws Exception {
        InviteUserRequest req = new InviteUserRequest();
        req.setUniqueUserId("XXXXXXXXXXXX"); // 12 char tapi tidak ada di DB

        mockMvc.perform(post("/api/v1/shared/pairing/invite")
                        .header("Authorization", bearerToken(guardianToken))
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(req)))
                .andExpect(result ->
                        assertThat(result.getResponse().getStatus())
                                .isGreaterThanOrEqualTo(400));
    }

    @Test
    @Order(4)
    @DisplayName("POST /shared/pairing/invite — User (bukan Guardian) coba invite → error")
    void invite_asUser_returnsError() throws Exception {
        InviteUserRequest req = new InviteUserRequest();
        req.setUniqueUserId("SOMEUSRXXXXX");

        mockMvc.perform(post("/api/v1/shared/pairing/invite")
                        .header("Authorization", bearerToken(userToken))
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(req)))
                .andExpect(result ->
                        assertThat(result.getResponse().getStatus())
                                .isGreaterThanOrEqualTo(400));
    }

    // ==========================================================================
    // Status PENDING
    // ==========================================================================

    @Test
    @Order(5)
    @DisplayName("GET /shared/pairing/status — User lihat status PENDING")
    void status_asUser_pending() throws Exception {
        mockMvc.perform(get("/api/v1/shared/pairing/status")
                        .header("Authorization", bearerToken(userToken)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.message").value("Status pairing"))
                .andExpect(jsonPath("$.data.status").value("PENDING"));
    }

    @Test
    @Order(6)
    @DisplayName("GET /shared/pairing/status — Guardian lihat status PENDING")
    void status_asGuardian_pending() throws Exception {
        mockMvc.perform(get("/api/v1/shared/pairing/status")
                        .header("Authorization", bearerToken(guardianToken)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.data.status").value("PENDING"));
    }

    // ==========================================================================
    // Accept → ACTIVE
    // ==========================================================================

    @Test
    @Order(7)
    @DisplayName("POST /shared/pairing/respond — User accept pairing → ACTIVE")
    void respond_accept_returnsActive() throws Exception {
        assertThat(pairingId).isGreaterThan(0);

        // PairingResponseRequest: fields = pairingId, accept (@Data → setter tersedia)
        PairingResponseRequest req = new PairingResponseRequest();
        req.setPairingId(pairingId);
        req.setAccept(true);

        mockMvc.perform(post("/api/v1/shared/pairing/respond")
                        .header("Authorization", bearerToken(userToken))
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(req)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.message").value("Pairing diterima"))
                .andExpect(jsonPath("$.data.status").value("ACTIVE"));
    }

    @Test
    @Order(8)
    @DisplayName("GET /shared/pairing/status — setelah accept, status ACTIVE")
    void status_afterAccept_isActive() throws Exception {
        mockMvc.perform(get("/api/v1/shared/pairing/status")
                        .header("Authorization", bearerToken(userToken)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.data.status").value("ACTIVE"));
    }

    @Test
    @Order(9)
    @DisplayName("GET /guardian/dashboard — setelah paired, Guardian bisa akses dashboard")
    void guardianDashboard_afterPairing_returns200() throws Exception {
        mockMvc.perform(get("/api/v1/guardian/dashboard")
                        .header("Authorization", bearerToken(guardianToken)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.message").value("Dashboard Guardian"));
    }

    // ==========================================================================
    // Unpair
    // ==========================================================================

    @Test
    @Order(10)
    @DisplayName("DELETE /shared/pairing/unpair — User unpair → 200 pairing diakhiri")
    void unpair_asUser_returns200() throws Exception {
        mockMvc.perform(delete("/api/v1/shared/pairing/unpair")
                        .header("Authorization", bearerToken(userToken)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.message").value("Pairing diakhiri"));
    }

    @Test
    @Order(11)
    @DisplayName("GET /shared/pairing/status — setelah unpair, tidak ada pairing ACTIVE")
    void status_afterUnpair_isNotActive() throws Exception {
        mockMvc.perform(get("/api/v1/shared/pairing/status")
                        .header("Authorization", bearerToken(userToken)))
                .andExpect(status().isOk())
                .andExpect(result -> {
                    String body = result.getResponse().getContentAsString();
                    JsonNode json = objectMapper.readTree(body);
                    String statusVal = json.path("data").path("status").asText("NONE");
                    assertThat(statusVal).isNotEqualTo("ACTIVE");
                });
    }

    // ==========================================================================
    // Skenario Reject
    // ==========================================================================

    @Test
    @Order(12)
    @DisplayName("Skenario Reject: Guardian invite baru → User tolak → REJECTED")
    void scenario_reject_flow() throws Exception {
        String rejectUserEmail     = "reject_user@walkguide.test";
        String rejectGuardianEmail = "reject_guardian@walkguide.test";

        JsonNode rejectUserData = registerAndGetFullResponse(rejectUserEmail, "pass123", "USER");
        String rejectUserToken    = rejectUserData.path("accessToken").asText();
        String rejectUniqueId     = rejectUserData.path("uniqueUserId").asText();

        JsonNode rejectGuardData = registerAndGetFullResponse(rejectGuardianEmail, "pass123", "GUARDIAN");
        String rejectGuardToken  = rejectGuardData.path("accessToken").asText();

        // Guardian invite
        InviteUserRequest inviteReq = new InviteUserRequest();
        inviteReq.setUniqueUserId(rejectUniqueId);

        String inviteResponse = mockMvc.perform(post("/api/v1/shared/pairing/invite")
                        .header("Authorization", bearerToken(rejectGuardToken))
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(inviteReq)))
                .andExpect(status().isOk())
                .andReturn().getResponse().getContentAsString();

        Long rejectPairingId = objectMapper.readTree(inviteResponse)
                .path("data").path("pairingId").asLong();

        // User tolak
        PairingResponseRequest rejectReq = new PairingResponseRequest();
        rejectReq.setPairingId(rejectPairingId);
        rejectReq.setAccept(false);

        mockMvc.perform(post("/api/v1/shared/pairing/respond")
                        .header("Authorization", bearerToken(rejectUserToken))
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(rejectReq)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.message").value("Pairing ditolak"))
                .andExpect(jsonPath("$.data.status").value("REJECTED"));
    }

    // ==========================================================================
    // Endpoint tanpa auth
    // ==========================================================================

    @Test
    @Order(13)
    @DisplayName("DELETE /shared/pairing/unpair — tanpa token → 403")
    void unpair_withoutToken_returns403() throws Exception {
        mockMvc.perform(delete("/api/v1/shared/pairing/unpair"))
                .andExpect(status().isForbidden());
    }

    @Test
    @Order(14)
    @DisplayName("GET /shared/pairing/status — tanpa token → 403")
    void status_withoutToken_returns403() throws Exception {
        mockMvc.perform(get("/api/v1/shared/pairing/status"))
                .andExpect(status().isForbidden());
    }
}