package com.ga.api.controller.product;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ga.api.service.product.ContractService;
import com.ga.api.support.AbstractControllerTest;
import com.ga.api.support.MockLoginUser;
import com.ga.common.menu.PermissionChecker;
import com.ga.common.model.PageResponse;
import com.ga.core.vo.product.ContractResp;
import com.ga.core.vo.product.ContractSaveReq;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;

import static org.mockito.ArgumentMatchers.*;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willDoNothing;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

class ContractControllerTest extends AbstractControllerTest {

    @Autowired ObjectMapper objectMapper;

    @MockBean ContractService service;
    @MockBean PermissionChecker permissionChecker;

    @BeforeEach
    void permitAll() {
        given(permissionChecker.hasPermission(anyLong(), anyString(), anyString())).willReturn(true);
    }

    // ---- 픽스처 ----

    private ContractResp sampleContract() {
        ContractResp r = new ContractResp();
        r.setContractId(10L);
        r.setPolicyNo("POL-2024-001");
        r.setContractorName("김계약");
        r.setStatus("ACTIVE");
        return r;
    }

    private ContractSaveReq validReq() {
        ContractSaveReq req = new ContractSaveReq();
        req.setAgentId(1L);
        req.setProductId(2L);
        req.setPolicyNo("POL-2024-002");
        req.setPremium(new BigDecimal("100000"));
        req.setContractDate(LocalDate.of(2024, 1, 1));
        return req;
    }

    // ---- 테스트 ----

    @Test
    @DisplayName("GET /api/contracts - 200 목록 반환")
    void list_ok() throws Exception {
        PageResponse<ContractResp> page = PageResponse.<ContractResp>builder()
                .list(List.of(sampleContract()))
                .pageNum(1).pageSize(10).total(1).pages(1)
                .build();
        given(service.list(any())).willReturn(page);

        mockMvc.perform(get("/api/contracts").with(MockLoginUser.admin()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true))
                .andExpect(jsonPath("$.data.total").value(1))
                .andExpect(jsonPath("$.data.list[0].policyNo").value("POL-2024-001"));
    }

    @Test
    @DisplayName("GET /api/contracts/{id} - 200 상세 반환")
    void detail_ok() throws Exception {
        given(service.detail(10L)).willReturn(sampleContract());

        mockMvc.perform(get("/api/contracts/10").with(MockLoginUser.admin()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.data.contractId").value(10));
    }

    @Test
    @DisplayName("POST /api/contracts - 200 등록 성공")
    void create_ok() throws Exception {
        given(service.create(any())).willReturn(10L);

        mockMvc.perform(post("/api/contracts")
                        .with(MockLoginUser.admin())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(validReq())))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.data").value(10));
    }

    @Test
    @DisplayName("POST /api/contracts - 400 policyNo 누락")
    void create_missingPolicyNo() throws Exception {
        ContractSaveReq req = new ContractSaveReq();
        req.setAgentId(1L);
        req.setProductId(2L);
        req.setPremium(new BigDecimal("100000"));
        req.setContractDate(LocalDate.of(2024, 1, 1));
        // policyNo 없음

        mockMvc.perform(post("/api/contracts")
                        .with(MockLoginUser.admin())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(req)))
                .andExpect(status().isBadRequest());
    }

    @Test
    @DisplayName("PUT /api/contracts/{id} - 200 수정 성공")
    void update_ok() throws Exception {
        willDoNothing().given(service).update(eq(10L), any());

        mockMvc.perform(put("/api/contracts/10")
                        .with(MockLoginUser.admin())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(validReq())))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.success").value(true));
    }

    @Test
    @DisplayName("GET /api/contracts - 401 인증 없는 요청")
    void list_unauthenticated() throws Exception {
        mockMvc.perform(get("/api/contracts"))
                .andExpect(status().isUnauthorized());
    }

    @Test
    @DisplayName("GET /api/contracts - 403 권한 없는 요청")
    void list_forbidden() throws Exception {
        given(permissionChecker.hasPermission(anyLong(), eq("CONTRACT_LIST"), eq("READ"))).willReturn(false);

        mockMvc.perform(get("/api/contracts").with(MockLoginUser.admin()))
                .andExpect(status().isForbidden());
    }
}
