package kr.itn.itnhub.system;

import kr.itn.itnhub.AbstractDbTest;
import kr.itn.itnhub.mattermost.MattermostClient;
import kr.itn.itnhub.mattermost.MattermostException;
import kr.itn.itnhub.org.Organization;
import kr.itn.itnhub.org.OrganizationMapper;
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 static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
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.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

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

    @Autowired
    MockMvc mvc;

    @Autowired
    OrganizationMapper orgMapper;

    @Autowired
    JdbcTemplate jdbc;

    @MockBean
    MattermostClient mattermost;

    private Long bothChannelsId;
    private Long noChannelId;

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

        bothChannelsId = seed("001", "채널있는기관");
        orgMapper.updateChannelIdMj(bothChannelsId, "ch-mj");
        orgMapper.updateChannelIdLaw(bothChannelsId, "ch-law");
        orgMapper.updateStage(bothChannelsId, 3);
        jdbc.update("insert into stage_history (org_id, stage) values (?, 3)", bothChannelsId);

        noChannelId = seed("002", "채널없는기관");
    }

    private Long seed(String orgNo, String orgName) {
        Organization org = new Organization();
        org.setOrgNo(orgNo);
        org.setOrgName(orgName);
        org.setChannelSlug(orgNo);
        orgMapper.upsertBySeed(org);
        return orgMapper.findByOrgNoAndOrgName(orgNo, orgName).getId();
    }

    @Test
    void 미리보기는_DB가_아는_채널만_보여준다() throws Exception {
        mvc.perform(get("/api/system/reset/preview"))
                .andExpect(status().isOk())
                // 채널 없는 기관은 아예 목록에 없다
                .andExpect(jsonPath("$.length()").value(1))
                .andExpect(jsonPath("$[0].orgNo").value("001"))
                .andExpect(jsonPath("$[0].stage").value(3))
                .andExpect(jsonPath("$[0].channels.length()").value(2))
                .andExpect(jsonPath("$[0].channels[0].kind").value("문정원"))
                .andExpect(jsonPath("$[0].channels[1].kind").value("법률검토"));
    }

    @Test
    void 미리보기는_아무것도_바꾸지_않는다() throws Exception {
        mvc.perform(get("/api/system/reset/preview")).andExpect(status().isOk());

        assertThat(orgMapper.findById(bothChannelsId).getChannelIdMj()).isEqualTo("ch-mj");
        verify(mattermost, never()).archiveChannel(anyString());
    }

    @Test
    void 확인문구가_틀리면_400이고_아무것도_지우지_않는다() throws Exception {
        mvc.perform(post("/api/system/reset/channels").with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"confirm\": \"초기화\"}"))
                .andExpect(status().isBadRequest());

        verify(mattermost, never()).archiveChannel(anyString());
        assertThat(orgMapper.findById(bothChannelsId).getChannelIdMj()).isEqualTo("ch-mj");
    }

    @Test
    void 초기화하면_채널을_보관하고_DB의_채널과_단계를_비운다() throws Exception {
        mvc.perform(post("/api/system/reset/channels").with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"confirm\": \"채널 초기화\"}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.archivedChannels").value(2))
                .andExpect(jsonPath("$.clearedOrgs").value(1))
                .andExpect(jsonPath("$.failures.length()").value(0));

        verify(mattermost).archiveChannel("ch-mj");
        verify(mattermost).archiveChannel("ch-law");

        Organization after = orgMapper.findById(bothChannelsId);
        assertThat(after.getChannelIdMj()).isNull();
        assertThat(after.getChannelIdLaw()).isNull();
        assertThat(after.getStage()).isNull();
        assertThat(jdbc.queryForObject("select count(*) from stage_history where org_id = ?",
                Integer.class, bothChannelsId)).isZero();
    }

    @Test
    void 담당자_배정과_권리확인_데이터는_건드리지_않는다() throws Exception {
        jdbc.update("insert into review_item (org_id, seq, review_result) values (?, 1, '이용가능')",
                bothChannelsId);
        jdbc.update("insert into work_memo (org_id, contact_name, body) values (?, '시스템', '메모')",
                bothChannelsId);

        mvc.perform(post("/api/system/reset/channels").with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"confirm\": \"채널 초기화\"}"))
                .andExpect(status().isOk());

        assertThat(jdbc.queryForObject("select count(*) from review_item where org_id = ?",
                Integer.class, bothChannelsId)).isEqualTo(1);
        assertThat(jdbc.queryForObject("select count(*) from work_memo where org_id = ?",
                Integer.class, bothChannelsId)).isEqualTo(1);
        // 기관 자체도 남는다
        assertThat(orgMapper.findById(bothChannelsId)).isNotNull();
    }

    @Test
    void 한쪽_채널이_실패하면_그_기관의_DB는_그대로_두고_실패를_보고한다() throws Exception {
        doThrow(new MattermostException("권한 없음")).when(mattermost).archiveChannel("ch-law");

        mvc.perform(post("/api/system/reset/channels").with(csrf())
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{\"confirm\": \"채널 초기화\"}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.archivedChannels").value(1))
                .andExpect(jsonPath("$.clearedOrgs").value(0))
                .andExpect(jsonPath("$.failures.length()").value(1));

        // 남은 채널을 다시 찾을 수 있어야 하므로 ID를 지우지 않는다
        assertThat(orgMapper.findById(bothChannelsId).getChannelIdLaw()).isEqualTo("ch-law");
    }
}
