package com.vaessl.app.connection;

import static com.vaessl.app.Mockdata.*;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.resttestclient.TestRestTemplate;
import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;

import com.github.tomakehurst.wiremock.http.Fault;
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
import com.github.tomakehurst.wiremock.junit5.WireMockTest;

import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;

import static org.assertj.core.api.Assertions.assertThat;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.vaessl.app.shared.Endpoint.*;
import static com.vaessl.app.exception.ErrorMessage.*;
import static com.vaessl.app.shared.ServiceType.*;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureTestRestTemplate
@WireMockTest
class HomeboxIntegrationTest {

        @Autowired
        TestRestTemplate restTemplate;

        @Autowired
        ConnectionRepository cRepository;

        String okJsonHomeboxResponse = """
                        {
                            "token": "fake-jwt-token",
                            "attachmentToken": "fake-attach",
                            "expiresAt": "2026-04-26T02:23:13Z"
                        }
                            """;

        /**
         * Returns Token and status code OK when login is successful.
         * 
         * @param wm the WiremockRuntimeInfo object
         */
        @Test
        void shouldReturnStatusOkWhenHomeboxCredentialsAreValid(WireMockRuntimeInfo wm) {

                stubFor(post(HOMEBOX_LOGIN.getValue()).willReturn(okJson(okJsonHomeboxResponse)));

                ResponseEntity<String> response = restTemplate.postForEntity(LOGIN.getValue(),
                                connectionRequest(wm), String.class);

                DocumentContext documentContext = JsonPath.parse(response.getBody());

                String serviceType = documentContext.read("$.serviceType");
                assertThat(serviceType).isEqualTo("HOMEBOX");

                String expiresAt = documentContext.read("$.expiresAt", String.class);
                assertThat(expiresAt).isEqualTo("2026-04-26T02:23:13Z");
                assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        }

        /**
         * Tests the Unauthorized custom exception.
         * 
         * @param wm the WiremockRuntimeInfo object
         */
        @Test
        void shouldFailToConnectWhenHomeboxReturnsUnauthorized(WireMockRuntimeInfo wm) {

                stubFor(post(HOMEBOX_LOGIN.getValue()).willReturn(unauthorized()));

                ResponseEntity<String> response = restTemplate.postForEntity(LOGIN.getValue(),
                                connectionRequest(wm), String.class);

                assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
                assertThat(response.getBody()).contains(UNAUTHORIZED_WRONG_LOGIN.getMessage());
        }

        /**
         * Tests a server error from the external api.
         * 
         * @param wm the WiremockRuntimeInfo object
         */
        @Test
        void shouldFailToConnectWhenHomeboxReturnsServiceUnavailable(WireMockRuntimeInfo wm) {

                stubFor(post(HOMEBOX_LOGIN.getValue()).willReturn(serviceUnavailable()));

                ResponseEntity<String> response = restTemplate.postForEntity(LOGIN.getValue(),
                                connectionRequest(wm), String.class);

                assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
                assertThat(response.getBody()).contains(SERVER_ERROR_GENERAL.getMessage());
        }

        /**
         * Checks when the service is unavailable or the app URL is wrong.
         */
        @Test
        void shouldReturnServiceUnavailableWhenHomeboxUrlIsWrong(WireMockRuntimeInfo wm) {

                stubFor(post(HOMEBOX_LOGIN.getValue())
                                .willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER)));

                ConnectionRequest badRequest = connectionRequest(wm);
                ResponseEntity<String> response = restTemplate.postForEntity(LOGIN.getValue(),
                                badRequest, String.class);

                assertThat(response.getStatusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
                assertThat(response.getBody())
                                .contains(SERVICE_UNAVAILABLE_UNREACHABLE_URL.getMessage());
        }

        /**
         * Checks if any login fields are empty since all of them are mandatory.
         */
        @Test
        void shouldReturnBadRequestWhenHomeboxFieldsAreEmpty() {

                ConnectionRequest emtpyRequest = new ConnectionRequest("", null, "", "", false);

                ResponseEntity<String> response = restTemplate.postForEntity(LOGIN.getValue(),
                                emtpyRequest, String.class);

                assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
                assertThat(response.getBody()).contains(BAD_REQUEST_EMPTY_FIELDS.getMessage());
        }

        /**
         * Test the exception when there is an input for serviceType but it's unsupported.
         */
        @Test
        void shouldReturnBadRequestWhenServiceTypeIsInvalid(WireMockRuntimeInfo wm) {
                String body = """
                                {"appUrl":"%s","serviceType":"wrong-service-type","username":"%s","password":"%s"}
                                """
                                .formatted(wm.getHttpBaseUrl(), MOCK_USER, MOCK_PASS);

                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                HttpEntity<String> entity = new HttpEntity<>(body, headers);

                ResponseEntity<String> response = restTemplate.exchange(LOGIN.getValue(),
                                HttpMethod.POST, entity, String.class);

                assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
        }

        /**
         * Tests the succesfull persistance of Homebox credential response to the database.
         * 
         * @param wm the WiremockRuntimeInfo object
         */
        @Test
        void shouldSaveHomeboxConnectionResponseToDb(WireMockRuntimeInfo wm) {

                stubFor(post(urlEqualTo(HOMEBOX_LOGIN.getValue()))
                                .willReturn(okJson(okJsonHomeboxResponse)));

                ConnectionRequest request = connectionRequest(wm);

                ResponseEntity<String> response =
                                restTemplate.postForEntity(LOGIN.getValue(), request, String.class);
                assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);

                ConnectionEntity dbEntry = cRepository.findByAppUrlAndUsername(request.appUrl(),
                                request.username());

                assertThat(dbEntry).isNotNull();
                assertThat(dbEntry.getAppUrl()).isEqualTo(request.appUrl());
                assertThat(dbEntry.getUsername()).isEqualTo(request.username());

                if (dbEntry instanceof HomeboxConnectionEntity hbE) {
                        assertThat(hbE.getToken()).isEqualTo("fake-jwt-token");
                        assertThat(hbE.getAttachmentToken()).isEqualTo("fake-attach");
                        assertThat(hbE.getExpiresAt().toString())
                                        .hasToString("2026-04-26T02:23:13Z");
                }
        }

        @Test
        void shouldReturnEmptyCredentialsExceptionWhenCredsAreMissing(WireMockRuntimeInfo wm) {
                ConnectionRequest missingCredentials = new ConnectionRequest(wm.getHttpBaseUrl(),
                                HOMEBOX, MOCK_USER, null, false);

                ResponseEntity<String> response = restTemplate.postForEntity(LOGIN.getValue(),
                                missingCredentials, String.class);

                assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
                assertThat(response.getBody()).contains(BAD_REQUEST_EMPTY_FIELDS.getMessage());
        }

        /**
         * Creates a valid connection request with a mock Api through WireMockRuntimeInfo.
         * 
         * @param wm the WiremockRuntimeInfo object
         * @return a mock api connection request.
         */
        private ConnectionRequest connectionRequest(WireMockRuntimeInfo wm) {
                return new ConnectionRequest(wm.getHttpBaseUrl(), HOMEBOX, MOCK_USER, MOCK_PASS,
                                null);
        }
}
