package com.vaessl.app.connection;

import static com.vaessl.app.Mockdata.*;

import static com.vaessl.app.shared.Endpoint.*;
import static com.vaessl.app.shared.ServiceType.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
import com.github.tomakehurst.wiremock.junit5.WireMockTest;

import jakarta.servlet.http.Cookie;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;

@SpringBootTest
@AutoConfigureMockMvc
@WireMockTest
class ConnectionControllerTest {

        @Autowired
        MockMvc mockMvc;

        private static final String LOGIN_PATH = LOGIN.getValue();
        private static final String STATUS_PATH = CONNECTION_STATUS.getValue();
        private static final String LOGOUT_PATH = "/connections/HOMEBOX";
        private static final String SESSION = "SESSION";

        private static final String VALID_HOMEBOX_RESPONSE = """
                        {
                                "token": "fake-jwt-token",
                                "attachmentToken": "fake-attach",
                                "expiresAt": "2099-01-01T00:00:00Z"
                        }
                        """;

        private static final String EXPIRED_HOMEBOX_RESPONSE = """
                        {
                                "token": "expired-token",
                                "attachmentToken": "fake-attach",
                                "expiresAt": "2000-01-01T00:00:00Z"
                        }
                        """;

        @Test
        void shouldReturnEmptyListWhenNoActiveSession() throws Exception {
                mockMvc.perform(get(STATUS_PATH)).andExpect(status().isOk())
                                .andExpect(content().json("[]"));
        }

        @Test
        void shouldReturnConnectionStatusWithConnectedTrueAfterLogin(WireMockRuntimeInfo wm)
                        throws Exception {
                WireMock.stubFor(WireMock.post(HOMEBOX_LOGIN.getValue())
                                .willReturn(WireMock.okJson(VALID_HOMEBOX_RESPONSE)));

                MvcResult loginResult = mockMvc
                                .perform(post(LOGIN_PATH).contentType(MediaType.APPLICATION_JSON)
                                                .content(connectionRequestBody(wm)))
                                .andExpect(status().isOk()).andReturn();

                Cookie sessionCookie = loginResult.getResponse().getCookie(SESSION);

                mockMvc.perform(get(STATUS_PATH).cookie(sessionCookie)).andExpect(status().isOk())
                                .andExpect(jsonPath("$.length()").value(1))
                                .andExpect(jsonPath("$[0].serviceType").value(HOMEBOX.name()))
                                .andExpect(jsonPath("$[0].username").value(MOCK_USER))
                                .andExpect(jsonPath("$[0].appUrl").value(wm.getHttpBaseUrl()))
                                .andExpect(jsonPath("$[0].connected").value(true));
        }

        @Test
        void shouldReturnConnectedFalseWhenStoredTokenIsExpired(WireMockRuntimeInfo wm)
                        throws Exception {
                WireMock.stubFor(WireMock.post(HOMEBOX_LOGIN.getValue())
                                .willReturn(WireMock.okJson(EXPIRED_HOMEBOX_RESPONSE)));

                MvcResult loginResult = mockMvc
                                .perform(post(LOGIN_PATH).contentType(MediaType.APPLICATION_JSON)
                                                .content(connectionRequestBody(wm)))
                                .andExpect(status().isOk()).andReturn();

                Cookie sessionCookie = loginResult.getResponse().getCookie(SESSION);

                mockMvc.perform(get(STATUS_PATH).cookie(sessionCookie)).andExpect(status().isOk())
                                .andExpect(jsonPath("$[0].serviceType").value(HOMEBOX.name()))
                                .andExpect(jsonPath("$[0].connected").value(false))
                                .andExpect(jsonPath("$[0].expiresAt")
                                                .value("2000-01-01T00:00:00Z"));
        }

        @Test
        void shouldReturn204NoContentOnLogout(WireMockRuntimeInfo wm) throws Exception {
                WireMock.stubFor(WireMock.post(HOMEBOX_LOGIN.getValue())
                                .willReturn(WireMock.okJson(VALID_HOMEBOX_RESPONSE)));

                MvcResult loginResult = mockMvc
                                .perform(post(LOGIN_PATH).contentType(MediaType.APPLICATION_JSON)
                                                .content(connectionRequestBody(wm)))
                                .andExpect(status().isOk()).andReturn();

                Cookie sessionCookie = loginResult.getResponse().getCookie(SESSION);

                mockMvc.perform(delete(LOGOUT_PATH).cookie(sessionCookie))
                                .andExpect(status().isNoContent());
        }

        @Test
        void shouldReturnEmptyStatusListAfterLogout(WireMockRuntimeInfo wm) throws Exception {
                WireMock.stubFor(WireMock.post(HOMEBOX_LOGIN.getValue())
                                .willReturn(WireMock.okJson(VALID_HOMEBOX_RESPONSE)));

                MvcResult loginResult = mockMvc
                                .perform(post(LOGIN_PATH).contentType(MediaType.APPLICATION_JSON)
                                                .content(connectionRequestBody(wm)))
                                .andExpect(status().isOk()).andReturn();

                Cookie sessionCookie = loginResult.getResponse().getCookie(SESSION);

                // Verify connected before logout
                mockMvc.perform(get(STATUS_PATH).cookie(sessionCookie)).andExpect(status().isOk())
                                .andExpect(jsonPath("$.length()").value(1));

                mockMvc.perform(delete(LOGOUT_PATH).cookie(sessionCookie))
                                .andExpect(status().isNoContent());

                // A new request (no session cookie, as in a fresh browser) returns no connections
                mockMvc.perform(get(STATUS_PATH)).andExpect(status().isOk())
                                .andExpect(content().json("[]"));
        }

        @Test
        void shouldReturn204WhenLogoutCalledWithNoActiveSession() throws Exception {
                mockMvc.perform(delete(LOGOUT_PATH)).andExpect(status().isNoContent());
        }

        private String connectionRequestBody(WireMockRuntimeInfo wm) {
                return """
                                {
                                        "appUrl": "%s",
                                        "serviceType": "HOMEBOX",
                                        "username": "%s",
                                        "password": "%s"
                                }
                                """.formatted(wm.getHttpBaseUrl(), MOCK_USER, MOCK_PASS);
        }
}
