package kr.itn.itnhub.mattermost;

import com.github.tomakehurst.wiremock.WireMockServer;
import kr.itn.itnhub.config.MattermostProperties;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.web.client.RestClient;

import java.net.http.HttpClient;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class MattermostRestClientTest {

    private WireMockServer server;
    private MattermostRestClient client;

    @BeforeEach
    void setUp() {
        server = new WireMockServer(options().dynamicPort());
        server.start();

        MattermostProperties props = new MattermostProperties(
                "http://localhost:" + server.port(),
                "test-token",
                "team123",
                "문정원",
                "법률검토");

        // 이 HTTP/1.1 고정은 테스트 하네스(WireMock/Jetty)만의 제약이다: JDK 21 HttpClient의
        // 기본 h2c 업그레이드 협상이 WireMock 상대로 POST 요청에서 간헐적으로 RST_STREAM을
        // 유발하기 때문에 여기서만 고정한다. 운영 환경은 HTTPS/ALPN으로 협상하므로 해당되지 않는다.
        HttpClient httpClient = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_1_1)
                .build();
        RestClient.Builder builder = RestClient.builder()
                .requestFactory(new JdkClientHttpRequestFactory(httpClient));

        client = new MattermostRestClient(props, builder);
    }

    @AfterEach
    void tearDown() {
        server.stop();
    }

    @Test
    void 내부명으로_채널을_찾으면_id를_돌려준다() {
        server.stubFor(get(urlEqualTo("/api/v4/teams/team123/channels/name/org-001-mj"))
                .willReturn(okJson("{\"id\":\"chan001mj\",\"name\":\"org-001-mj\"}")));

        assertThat(client.findChannelIdByInternalName("org-001-mj"))
                .contains("chan001mj");
    }

    @Test
    void 내부명으로_채널이_없으면_빈값이다() {
        server.stubFor(get(urlEqualTo("/api/v4/teams/team123/channels/name/org-999-mj"))
                .willReturn(aResponse().withStatus(404)));

        assertThat(client.findChannelIdByInternalName("org-999-mj")).isEmpty();
    }

    @Test
    void 토큰을_Authorization_헤더로_보낸다() {
        server.stubFor(get(urlEqualTo("/api/v4/teams/team123/channels/name/org-001-mj"))
                .willReturn(okJson("{\"id\":\"chan001mj\"}")));

        client.findChannelIdByInternalName("org-001-mj");

        server.verify(getRequestedFor(urlEqualTo("/api/v4/teams/team123/channels/name/org-001-mj"))
                .withHeader("Authorization", equalTo("Bearer test-token")));
    }

    @Test
    void 표시명으로_팀전체_채널목록에서_id를_찾는다() {
        server.stubFor(get(urlPathEqualTo("/api/v4/teams/team123/channels"))
                .withQueryParam("page", equalTo("0"))
                .withQueryParam("per_page", equalTo("200"))
                .willReturn(okJson("""
                        [
                          {"id":"aaa","display_name":"001_국제방송교류재단 (문정원)"},
                          {"id":"bbb","display_name":"001_국제방송교류재단 (법률검토)"}
                        ]
                        """)));

        assertThat(client.findChannelIdByDisplayName("001_국제방송교류재단 (법률검토)"))
                .contains("bbb");
    }

    @Test
    void 표시명이_첫페이지에_없으면_빈값이다() {
        server.stubFor(get(urlPathEqualTo("/api/v4/teams/team123/channels"))
                .withQueryParam("page", equalTo("0"))
                .withQueryParam("per_page", equalTo("200"))
                .willReturn(okJson("""
                        [
                          {"id":"aaa","display_name":"001_국제방송교류재단 (문정원)"}
                        ]
                        """)));

        assertThat(client.findChannelIdByDisplayName("002_없는기관 (문정원)")).isEmpty();
    }

    @Test
    void 표시명_조회가_두번째_페이지에서_찾으면_id를_돌려준다() {
        server.stubFor(get(urlPathEqualTo("/api/v4/teams/team123/channels"))
                .withQueryParam("page", equalTo("0"))
                .withQueryParam("per_page", equalTo("200"))
                .willReturn(okJson(fullPageJson(200, "p0", null))));

        server.stubFor(get(urlPathEqualTo("/api/v4/teams/team123/channels"))
                .withQueryParam("page", equalTo("1"))
                .withQueryParam("per_page", equalTo("200"))
                .willReturn(okJson(fullPageJson(5, "p1", "004_찾는기관 (법률검토)"))));

        assertThat(client.findChannelIdByDisplayName("004_찾는기관 (법률검토)"))
                .contains("p1-match");
    }

    @Test
    void 표시명_조회가_두_페이지를_모두_소진하면_빈값이다() {
        server.stubFor(get(urlPathEqualTo("/api/v4/teams/team123/channels"))
                .withQueryParam("page", equalTo("0"))
                .withQueryParam("per_page", equalTo("200"))
                .willReturn(okJson(fullPageJson(200, "p0", null))));

        server.stubFor(get(urlPathEqualTo("/api/v4/teams/team123/channels"))
                .withQueryParam("page", equalTo("1"))
                .withQueryParam("per_page", equalTo("200"))
                .willReturn(okJson(fullPageJson(10, "p1", null))));

        assertThat(client.findChannelIdByDisplayName("005_존재하지않는기관 (법률검토)")).isEmpty();
    }

    /** 팀 채널 목록 한 페이지를 흉내낸 JSON을 만든다. matchDisplayName이 있으면 마지막에 추가한다. */
    private static String fullPageJson(int fillerCount, String idPrefix, String matchDisplayName) {
        StringBuilder sb = new StringBuilder("[");
        for (int i = 0; i < fillerCount; i++) {
            if (i > 0) {
                sb.append(",");
            }
            sb.append("{\"id\":\"").append(idPrefix).append("-filler").append(i)
                    .append("\",\"display_name\":\"filler-").append(idPrefix).append("-").append(i)
                    .append("\"}");
        }
        if (matchDisplayName != null) {
            if (fillerCount > 0) {
                sb.append(",");
            }
            sb.append("{\"id\":\"").append(idPrefix).append("-match\",\"display_name\":\"")
                    .append(matchDisplayName).append("\"}");
        }
        sb.append("]");
        return sb.toString();
    }

    @Test
    void 비공개_채널을_생성하고_id를_돌려준다() {
        server.stubFor(post(urlEqualTo("/api/v4/channels"))
                .willReturn(okJson("{\"id\":\"newchan\"}")));

        String id = client.createPrivateChannel("org-002-mj", "002_세종학당재단 (문정원)");

        assertThat(id).isEqualTo("newchan");

        server.verify(postRequestedFor(urlEqualTo("/api/v4/channels"))
                .withRequestBody(matchingJsonPath("$.team_id", equalTo("team123")))
                .withRequestBody(matchingJsonPath("$.name", equalTo("org-002-mj")))
                .withRequestBody(matchingJsonPath("$.display_name",
                        equalTo("002_세종학당재단 (문정원)")))
                .withRequestBody(matchingJsonPath("$.type", equalTo("P"))));
    }

    @Test
    void 서버가_오류를_주면_MattermostException을_던진다() {
        server.stubFor(post(urlEqualTo("/api/v4/channels"))
                .willReturn(aResponse().withStatus(500).withBody("boom")));

        assertThatThrownBy(() -> client.createPrivateChannel("org-003-mj", "003_기관 (문정원)"))
                .isInstanceOf(MattermostException.class);
    }
}
