package com.justpickup.userservice.domain.user.web;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.justpickup.userservice.config.TestConfig;
import com.justpickup.userservice.domain.user.dto.CustomerDto;
import com.justpickup.userservice.domain.user.exception.DuplicateUserEmail;
import com.justpickup.userservice.domain.user.exception.NotExistUserException;
import com.justpickup.userservice.domain.user.service.UserService;
import com.justpickup.userservice.global.dto.Code;
import com.justpickup.userservice.global.security.SecurityConfig;
import com.justpickup.userservice.global.utils.CookieProvider;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
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 org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willThrow;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get;
import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post;
import static org.springframework.restdocs.payload.PayloadDocumentation.*;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;


@WebMvcTest(controllers = UserController.class,
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = SecurityConfig.class)}
)
@AutoConfigureMockMvc(addFilters = false)
@Import(TestConfig.class)
@AutoConfigureRestDocs(uriHost = "127.0.0.1", uriPort = 8001)
class UserControllerTest {

    @Autowired
    ObjectMapper objectMapper;

    @Autowired
    MockMvc mockMvc;

    @MockBean
    UserService userService;

    @SpyBean
    CookieProvider cookieProvider;

    @Test
    @DisplayName("회원 조회")
    void getCustomer() throws Exception {
        // GIVEN
        long userId = 1L;

        CustomerDto willReturnDto = CustomerDto.builder()
                .id(1L)
                .name("이름")
                .password("password!@#")
                .phoneNumber("010-1234-5678")
                .build();

        given(userService.findCustomerByUserId(userId))
                .willReturn(willReturnDto);

        // WHEN
        ResultActions actions = mockMvc.perform(get("/customer/{userId}", userId));

        // THEN
        actions.andExpect(status().isOk())
                .andExpect(jsonPath("code").value(Code.SUCCESS.name()))
                .andExpect(jsonPath("message").value(""))
                .andExpect(jsonPath("data.userId").value(1))
                .andExpect(jsonPath("data.userName").value("이름"))
                .andExpect(jsonPath("data.phoneNumber").value("010-1234-5678"))
                .andDo(print())
                .andDo(document("customer-get",
                        pathParameters(
                                parameterWithName("userId").description("회원 고유번호")
                        ),
                        responseFields(
                                fieldWithPath("code").description("결과코드 SUCCESS/ERROR"),
                                fieldWithPath("message").description("메시지"),
                                fieldWithPath("data.userId").description("회원 고유번호"),
                                fieldWithPath("data.userName").description("회원 이름"),
                                fieldWithPath("data.phoneNumber").description("회원 휴대폰 번호")
                        ))
                )
        ;
    }

    @Test
    @DisplayName("회원 조회 - 존재하지 않는 회원")
    void getCustomerNotExistUserException() throws Exception {
        // GIVEN
        long notExistUserId = 9999L;
        String message = "존재하지 않는 회원 입니다.";
        given(userService.findCustomerByUserId(notExistUserId))
                .willThrow(new NotExistUserException(message));

        // WHEN
        ResultActions actions = mockMvc.perform(get("/customer/{userId}", notExistUserId));

        // THEN
        actions.andExpect(status().isConflict())
                .andExpect(jsonPath("code").value(Code.ERROR.name()))
                .andExpect(jsonPath("message").value(message))
                .andExpect(jsonPath("data").isEmpty())
                .andDo(print())
                .andDo(document("customer-get-notExistUserException",
                        pathParameters(
                                parameterWithName("userId").description("회원 고유번호")
                        ),
                        responseFields(
                                fieldWithPath("code").description("결과코드 SUCCESS/ERROR"),
                                fieldWithPath("message").description("메시지"),
                                fieldWithPath("data").description("데이터")
                        )
                        ))
                ;
    }

    @Test
    @DisplayName("회원가입 - 점주")
    void registerStoreOwner() throws Exception {
        UserController.JoinStoreOwnerRequest requestBody =
                new UserController.JoinStoreOwnerRequest("test@naver.com", "1234", "Park",
                        "010-1234-5678", "1234");

        ResultActions actions = mockMvc.perform(post("/store-owner")
                .content(objectMapper.writeValueAsString(requestBody))
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
        );

        actions.andExpect(status().isCreated())
                .andDo(print())
                .andDo(document("storeOwner-post",
                        requestFields(
                                fieldWithPath("email").description("이메일"),
                                fieldWithPath("password").description("비밀번호"),
                                fieldWithPath("name").description("이름"),
                                fieldWithPath("phoneNumber").description("휴대폰번호"),
                                fieldWithPath("businessNumber").description("사업자등록번호")
                        ),
                        responseFields(
                                fieldWithPath("code").description("결과코드 SUCCESS/ERROR"),
                                fieldWithPath("message").description("메시지"),
                                fieldWithPath("data").description("데이터")
                        )
                ))
        ;
    }

    @Test
    @DisplayName("회원가입 - 점주 : 존재하는 회원 이메일")
    void registerStoreOwnerDuplicateUserEmailException() throws Exception {
        String email = "test@naver.com";
        UserController.JoinStoreOwnerRequest requestBody =
                new UserController.JoinStoreOwnerRequest(email, "1234", "Park",
                        "010-1234-5678", "1234");

        willThrow(new DuplicateUserEmail(email + "은 중복된 이메일입니다."))
                .given(userService).saveStoreOwner(any());

        ResultActions actions = mockMvc.perform(post("/store-owner")
                .content(objectMapper.writeValueAsString(requestBody))
                .contentType(MediaType.APPLICATION_JSON)
                .accept(MediaType.APPLICATION_JSON)
        );

        actions.andExpect(status().isConflict())
                .andDo(print())
                .andDo(document("storeOwner-post-duplicateUserEmailException",
                        requestFields(
                                fieldWithPath("email").description("이메일"),
                                fieldWithPath("password").description("비밀번호"),
                                fieldWithPath("name").description("이름"),
                                fieldWithPath("phoneNumber").description("휴대폰번호"),
                                fieldWithPath("businessNumber").description("사업자등록번호")
                        ),
                        responseFields(
                                fieldWithPath("code").description("결과코드 SUCCESS/ERROR"),
                                fieldWithPath("message").description("메시지"),
                                fieldWithPath("data").description("데이터")
                        )
                ))
        ;
    }
}