package kr.itn.itnhub.org;

import kr.itn.itnhub.AbstractDbTest;
import kr.itn.itnhub.mattermost.MattermostClient;
import kr.itn.itnhub.mattermost.MattermostException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;

import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.nullValue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@AutoConfigureMockMvc
@WithMockUser(roles = "ADMIN")
class OrganizationControllerTest extends AbstractDbTest {

    @Autowired
    MockMvc mvc;

    @Autowired
    OrganizationMapper mapper;

    @Autowired
    JdbcTemplate jdbc;

    @MockBean
    MattermostClient mattermost;

    private Long orgId;

    @BeforeEach
    void setUp() {
        jdbc.update("delete from organization");

        Organization org = new Organization();
        org.setOrgNo("001");
        org.setOrgName("국제방송교류재단");
        org.setChannelSlug("001");
        mapper.upsertBySeed(org);

        orgId = mapper.findAll().get(0).getId();
    }

    @Test
    void 기관_목록을_상태와_함께_돌려준다() throws Exception {
        mvc.perform(get("/api/orgs"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[0].orgNo").value("001"))
                .andExpect(jsonPath("$[0].orgName").value("국제방송교류재단"))
                .andExpect(jsonPath("$[0].status").value("INFO_PENDING"));
    }

    @Test
    void 담당자정보를_저장하면_생성가능_상태가_된다() throws Exception {
        mvc.perform(put("/api/orgs/{id}/contact", orgId)
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("""
                                {
                                  "deptName": "데이터정보화팀",
                                  "managerName": "송민지",
                                  "managerTitle": "과장",
                                  "managerPhone": "02-3475-5434",
                                  "managerEmail": "ming@arirang.com"
                                }
                                """))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.status").value("READY"));

        assertThat(mapper.findById(orgId).getDeptName()).isEqualTo("데이터정보화팀");
    }

    @Test
    void 문정원_담당자와_변호사_정보를_포함해_저장하면_응답과_재조회에_모두_반영된다() throws Exception {
        mvc.perform(put("/api/orgs/{id}/contact", orgId)
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("""
                                {
                                  "deptName": "데이터정보화팀",
                                  "managerName": "송민지",
                                  "managerTitle": "과장",
                                  "managerPhone": "02-3475-5434",
                                  "managerEmail": "ming@arirang.com",
                                  "mjDeptName": "문화체육관광부 저작권정책과",
                                  "mjManagerName": "김문정",
                                  "mjManagerPhone": "02-1234-5678",
                                  "mjManagerEmail": "mj@mcst.go.kr",
                                  "lawyerName": "이변호",
                                  "lawyerPhone": "02-9876-5432",
                                  "lawyerEmail": "lawyer@lawfirm.kr",
                                  "lawyerAssignedDate": "2026-07-21"
                                }
                                """))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.mjDeptName").value("문화체육관광부 저작권정책과"))
                .andExpect(jsonPath("$.mjManagerName").value("김문정"))
                .andExpect(jsonPath("$.mjManagerPhone").value("02-1234-5678"))
                .andExpect(jsonPath("$.mjManagerEmail").value("mj@mcst.go.kr"))
                .andExpect(jsonPath("$.lawyerName").value("이변호"))
                .andExpect(jsonPath("$.lawyerPhone").value("02-9876-5432"))
                .andExpect(jsonPath("$.lawyerEmail").value("lawyer@lawfirm.kr"))
                .andExpect(jsonPath("$.lawyerAssignedDate").value("2026-07-21"));

        mvc.perform(get("/api/orgs"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[0].mjManagerName").value("김문정"))
                .andExpect(jsonPath("$[0].lawyerName").value("이변호"));
    }

    @Test
    void 문정원_변호사_정보없이_기존방식_페이로드만으로도_저장에_성공한다() throws Exception {
        mvc.perform(put("/api/orgs/{id}/contact", orgId)
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("""
                                {
                                  "deptName": "데이터정보화팀",
                                  "managerName": "송민지",
                                  "managerTitle": "과장",
                                  "managerPhone": "02-3475-5434",
                                  "managerEmail": "ming@arirang.com"
                                }
                                """))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.status").value("READY"))
                .andExpect(jsonPath("$.mjManagerName").value(nullValue()))
                .andExpect(jsonPath("$.lawyerName").value(nullValue()));
    }

    @Test
    void 존재하지_않는_기관ID로_담당자정보를_수정하면_404다() throws Exception {
        long missingId = orgId + 999999L;

        mvc.perform(put("/api/orgs/{id}/contact", missingId)
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("""
                                {
                                  "deptName": "데이터정보화팀",
                                  "managerName": "송민지",
                                  "managerTitle": "과장",
                                  "managerPhone": "02-3475-5434",
                                  "managerEmail": "ming@arirang.com"
                                }
                                """))
                .andExpect(status().isNotFound())
                .andExpect(jsonPath("$.message").value(containsString(String.valueOf(missingId))));
    }

    @Test
    void 필수값이_비면_400이다() throws Exception {
        mvc.perform(put("/api/orgs/{id}/contact", orgId)
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("""
                                {
                                  "deptName": "",
                                  "managerName": "송민지",
                                  "managerPhone": "02-3475-5434",
                                  "managerEmail": "ming@arirang.com"
                                }
                                """))
                .andExpect(status().isBadRequest());
    }

    /**
     * Finding I6 회귀 테스트: 검증 실패 응답 본문이 필드명을 그대로 노출하는 원시 JSON
     * 블롭(예: {"deptName":"must not be blank"})이 아니라, 운영자가 읽을 수 있는
     * 한글 라벨(부서명 등)이 담긴 {@link kr.itn.itnhub.config.ApiError} 메시지여야 한다.
     */
    @Test
    void 필수값이_비면_한글_필드명이_담긴_메시지를_돌려준다() throws Exception {
        mvc.perform(put("/api/orgs/{id}/contact", orgId)
                        .with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("""
                                {
                                  "deptName": "",
                                  "managerName": "",
                                  "managerPhone": "02-3475-5434",
                                  "managerEmail": "ming@arirang.com"
                                }
                                """))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.message").value(containsString("부서명")))
                .andExpect(jsonPath("$.message").value(containsString("담당자명")));
    }

    @Test
    void 채널생성을_요청하면_결과를_돌려준다() throws Exception {
        Organization ready = mapper.findById(orgId);
        ready.setDeptName("데이터정보화팀");
        ready.setManagerName("송민지");
        ready.setManagerTitle("과장");
        ready.setManagerPhone("02-3475-5434");
        ready.setManagerEmail("ming@arirang.com");
        mapper.updateContact(ready);

        when(mattermost.findChannelIdByInternalName(anyString())).thenReturn(Optional.empty());
        when(mattermost.findChannelIdByDisplayName(anyString())).thenReturn(Optional.empty());
        when(mattermost.createPrivateChannel(anyString(), anyString())).thenReturn("chan");

        mvc.perform(post("/api/orgs/{id}/channels", orgId).with(csrf()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.mj").value("CREATED"))
                .andExpect(jsonPath("$.law").value("CREATED"));
    }

    @Test
    void 담당자정보없이_채널생성하면_실패결과를_돌려준다() throws Exception {
        mvc.perform(post("/api/orgs/{id}/channels", orgId).with(csrf()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.mj").value("FAILED"))
                .andExpect(jsonPath("$.law").value("FAILED"))
                .andExpect(jsonPath("$.message").value(containsString("담당자 정보")));
    }

    /**
     * Finding 2 회귀 테스트: 문정원 채널은 성공하고 법률검토 채널만 실패하는 실제 혼합
     * 결과를 컨트롤러 경계에서 검증한다. 기존 테스트는 전부 성공 아니면(둘 다 성공)
     * 담당자 정보 누락으로 둘 다 강제 FAILED인 경로만 다뤄, 진짜 부분 실패가 HTTP
     * 200으로 내려가는지는 한 번도 실행되지 않았다.
     */
    @Test
    void 법률검토만_실패해도_200과_부분실패_결과를_돌려준다() throws Exception {
        Organization ready = mapper.findById(orgId);
        ready.setDeptName("데이터정보화팀");
        ready.setManagerName("송민지");
        ready.setManagerTitle("과장");
        ready.setManagerPhone("02-3475-5434");
        ready.setManagerEmail("ming@arirang.com");
        mapper.updateContact(ready);

        when(mattermost.findChannelIdByInternalName(anyString())).thenReturn(Optional.empty());
        when(mattermost.findChannelIdByDisplayName(anyString())).thenReturn(Optional.empty());
        when(mattermost.createPrivateChannel(eq("org-001-mj"), anyString())).thenReturn("id-mj");
        when(mattermost.createPrivateChannel(eq("org-001-law"), anyString()))
                .thenThrow(new MattermostException("법률검토 서버 오류"));

        mvc.perform(post("/api/orgs/{id}/channels", orgId).with(csrf()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.mj").value("CREATED"))
                .andExpect(jsonPath("$.law").value("FAILED"))
                .andExpect(jsonPath("$.message").value(containsString("법률검토")));
    }
}
