package com.walkguide.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.walkguide.dto.request.FcmTokenRequest;
import com.walkguide.dto.request.LoginRequest;
import com.walkguide.dto.request.RefreshTokenRequest;
import com.walkguide.dto.request.RegisterRequest;
import com.walkguide.dto.response.AuthDataResponse;
import com.walkguide.security.SecurityHelper;
import com.walkguide.service.AuthService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import com.walkguide.security.JwtAuthFilter;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@AutoConfigureMockMvc(addFilters = false)
@WebMvcTest(AuthController.class)
@DisplayName("AuthController Unit Tests")
class AuthControllerTest {

    @MockBean private JwtAuthFilter jwtAuthFilter;

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @MockBean
    private AuthService authService;

    private AuthDataResponse sampleAuthData;

    @BeforeEach
    void setUp() {
        sampleAuthData = AuthDataResponse.builder()
                .accessToken("access-token-xyz")
                .refreshToken("refresh-token-xyz")
                .role("ROLE_USER")
                .userId(1L)
                .displayName("Test User")
                .uniqueUserId("ABC123DEF456")
                .build();
    }

    // ===== PING =====

    @Test
    @DisplayName("GET /api/v1/auth/ping - harus return 200 pong")
    void ping_shouldReturn200WithPong() throws Exception {
        mockMvc.perform(get("/api/v1/auth/ping"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.data").value("pong"))
                .andExpect(jsonPath("$.message").value("Server aktif"));
    }

    // ===== REGISTER =====

    @Test
    @DisplayName("POST /api/v1/auth/register - valid request harus return 200 dengan token")
    void register_validRequest_shouldReturn200() throws Exception {
        RegisterRequest req = new RegisterRequest();
        req.setEmail("new@test.com");
        req.setPassword("password123");
        req.setDisplayName("New User");
        req.setRole("USER");

        when(authService.register(any(RegisterRequest.class))).thenReturn(sampleAuthData);

        mockMvc.perform(post("/api/v1/auth/register")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(req)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.message").value("Registrasi berhasil"))
                .andExpect(jsonPath("$.data.accessToken").value("access-token-xyz"))
                .andExpect(jsonPath("$.data.role").value("ROLE_USER"));

        verify(authService).register(any(RegisterRequest.class));
    }

    @Test
    @DisplayName("POST /api/v1/auth/register - email kosong harus return 400")
    void register_emptyEmail_shouldReturn400() throws Exception {
        RegisterRequest req = new RegisterRequest();
        req.setEmail("");
        req.setPassword("password123");
        req.setDisplayName("New User");
        req.setRole("USER");

        mockMvc.perform(post("/api/v1/auth/register")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(req)))
                .andExpect(status().isBadRequest());

        verify(authService, never()).register(any());
    }

    @Test
    @DisplayName("POST /api/v1/auth/register - password kurang dari 6 karakter harus return 400")
    void register_shortPassword_shouldReturn400() throws Exception {
        RegisterRequest req = new RegisterRequest();
        req.setEmail("user@test.com");
        req.setPassword("abc");
        req.setDisplayName("User");
        req.setRole("USER");

        mockMvc.perform(post("/api/v1/auth/register")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(req)))
                .andExpect(status().isBadRequest());

        verify(authService, never()).register(any());
    }

    @Test
    @DisplayName("POST /api/v1/auth/register - format email tidak valid harus return 400")
    void register_invalidEmailFormat_shouldReturn400() throws Exception {
        RegisterRequest req = new RegisterRequest();
        req.setEmail("bukan-email");
        req.setPassword("password123");
        req.setDisplayName("User");
        req.setRole("USER");

        mockMvc.perform(post("/api/v1/auth/register")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(req)))
                .andExpect(status().isBadRequest());

        verify(authService, never()).register(any());
    }

    // ===== LOGIN =====

    @Test
    @DisplayName("POST /api/v1/auth/login - valid credentials harus return 200 dengan token")
    void login_validCredentials_shouldReturn200() throws Exception {
        LoginRequest req = new LoginRequest();
        req.setEmail("user@test.com");
        req.setPassword("password123");

        when(authService.login(any(LoginRequest.class))).thenReturn(sampleAuthData);

        mockMvc.perform(post("/api/v1/auth/login")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(req)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.message").value("Login berhasil"))
                .andExpect(jsonPath("$.data.accessToken").value("access-token-xyz"))
                .andExpect(jsonPath("$.data.userId").value(1));

        verify(authService).login(any(LoginRequest.class));
    }

    @Test
    @DisplayName("POST /api/v1/auth/login - service throw RuntimeException harus return 500")
    void login_serviceThrows_shouldReturn500() throws Exception {
        LoginRequest req = new LoginRequest();
        req.setEmail("user@test.com");
        req.setPassword("wrong");

        when(authService.login(any(LoginRequest.class)))
                .thenThrow(new RuntimeException("Password salah"));

        mockMvc.perform(post("/api/v1/auth/login")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(req)))
                .andExpect(status().isInternalServerError());
    }

    // ===== REFRESH =====

    @Test
    @DisplayName("POST /api/v1/auth/refresh - token valid harus return token baru")
    void refresh_validToken_shouldReturn200() throws Exception {
        RefreshTokenRequest req = new RefreshTokenRequest();
        req.setRefreshToken("valid-refresh-token");

        AuthDataResponse newData = AuthDataResponse.builder()
                .accessToken("new-access-token")
                .refreshToken("valid-refresh-token")
                .role("ROLE_USER")
                .userId(1L)
                .build();

        when(authService.refreshToken("valid-refresh-token")).thenReturn(newData);

        mockMvc.perform(post("/api/v1/auth/refresh")
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(req)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.message").value("Token diperbarui"))
                .andExpect(jsonPath("$.data.accessToken").value("new-access-token"));

        verify(authService).refreshToken("valid-refresh-token");
    }

    // ===== LOGOUT =====

    @Test
    @DisplayName("POST /api/v1/auth/logout - authenticated user harus return 200")
    @WithMockUser(username = "1", roles = "USER")
    void logout_authenticated_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> securityHelper = mockStatic(SecurityHelper.class)) {
            securityHelper.when(SecurityHelper::getCurrentUserId).thenReturn(1L);
            doNothing().when(authService).logout(1L);

            mockMvc.perform(post("/api/v1/auth/logout")
                            .with(csrf()))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.success").value(true))
                    .andExpect(jsonPath("$.message").value("Logout berhasil"));

            verify(authService).logout(1L);
        }
    }

    // ===== UPDATE FCM TOKEN =====

    @Test
    @DisplayName("PUT /api/v1/auth/fcm-token - authenticated user harus update FCM token")
    @WithMockUser(username = "1", roles = "USER")
    void updateFcmToken_authenticated_shouldReturn200() throws Exception {
        try (MockedStatic<SecurityHelper> securityHelper = mockStatic(SecurityHelper.class)) {
            securityHelper.when(SecurityHelper::getCurrentUserId).thenReturn(1L);

            FcmTokenRequest req = new FcmTokenRequest();
            req.setFcmToken("fcm-token-new-value");

            doNothing().when(authService).updateFcmToken(eq(1L), eq("fcm-token-new-value"));

            mockMvc.perform(put("/api/v1/auth/fcm-token")
                            .with(csrf())
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(req)))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.success").value(true))
                    .andExpect(jsonPath("$.message").value("FCM token diperbarui"));

            verify(authService).updateFcmToken(1L, "fcm-token-new-value");
        }
    }
}
