package de.avatic.lcc.controller.configuration;


import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;

import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@Transactional
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Sql(scripts = {"classpath:master_data/countries_properties.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = {"classpath:master_data/countries_properties-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public class CountryControllerIntegrationTest {

    protected static final String BASE_URL = "/api/countries";
    @Autowired
    protected MockMvc mockMvc;
    @Autowired
    protected JdbcTemplate jdbcTemplate;
    @Autowired
    protected ObjectMapper objectMapper;

    @Test
    @DisplayName("get countries with default pagination")
    void countriesDefaultPagination() throws Exception {
        mockMvc.perform(get(BASE_URL + "/"))
                .andExpect(status().isOk())
                .andExpect(header().string("X-Total-Count", "250"))
                .andExpect(header().string("X-Page-Count", "13"))
                .andExpect(header().string("X-Current-Page", "1"))
                .andExpect(jsonPath("$", hasSize(20)))
                .andReturn();

    }

    @Test
    @DisplayName("get countries with custom pagination")
    void countriesCustomPagination() throws Exception {
        mockMvc.perform(get(BASE_URL + "/")
                        .param("limit", "2")
                        .param("page", "15"))
                .andExpect(status().isOk())
                .andExpect(header().string("X-Total-Count", "250"))
                .andExpect(header().string("X-Page-Count", "125"))
                .andExpect(header().string("X-Current-Page", "15"))
                .andExpect(jsonPath("$", hasSize(2)));
    }

    @Test
    @DisplayName("get countries with filter region, non empty")
    void countriesFilteredByNAM() throws Exception {
        mockMvc.perform(get(BASE_URL + "/")
                        .param("filter", "NAM"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(5)))
                .andExpect(jsonPath("$[0].region_code").value("NAM"));
    }

    @Test
    @DisplayName("get countries with filter, empty result")
    void countriesFilteredByXyz() throws Exception {
        mockMvc.perform(get(BASE_URL + "/")
                        .param("filter", "XYZ"))
                .andExpect(status().isOk())
                .andExpect(header().string("X-Total-Count", "0"))
                .andExpect(jsonPath("$", hasSize(0)));
    }

    @Test
    @DisplayName("get countries with filter country, non empty")
    void countriesFilteredByDe() throws Exception {
        mockMvc.perform(get(BASE_URL + "/")
                        .param("filter", "DE"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(1)))
                .andExpect(jsonPath("$[0].iso_code").value("DE"));
    }

    @Test
    @DisplayName("get all countries without pagination")
    void allCountries() throws Exception {
        mockMvc.perform(get(BASE_URL + "/all"))
                .andExpect(status().isOk())
                .andExpect(header().string("X-Total-Count", "250"))
                .andExpect(jsonPath("$", hasSize(250)));
    }

    @Test
    @DisplayName("get all countries filtered by EMEA region")
    void allCountriesFilteredByEmea() throws Exception {
        mockMvc.perform(get(BASE_URL + "/all")
                        .param("filter", "EMEA"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(139))); // Nur DE, da XX deprecated
    }

    @Test
    @DisplayName("get country details by ID")
    void getCountryDetailsById() throws Exception {
        final String isoCode = "DE";

        Integer id = getCountryId(isoCode);

        mockMvc.perform(get(BASE_URL + "/" + id))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.iso_code").value(isoCode))
                .andExpect(jsonPath("$.region_code").value("EMEA"))
                .andExpect(jsonPath("$.id").value(id))
                .andExpect(jsonPath("$.properties[?(@.external_mapping_id=='WAGE')].current_value").value("1.00"));
    }

    private Integer getCountryId(String isoCode) {
        String query = "SELECT id FROM country WHERE iso_code = ? LIMIT 1";
        return jdbcTemplate.queryForObject(query, Integer.class, isoCode);
    }

    @Test
    @DisplayName("get country details by ISO code")
    void getCountryDetailsByIsoCode() throws Exception {
        final String isoCode = "US";

        Integer id = getCountryId("US");

        mockMvc.perform(get(BASE_URL + "/" + isoCode))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.iso_code").value(isoCode))
                .andExpect(jsonPath("$.region_code").value("NAM"))
                .andExpect(jsonPath("$.id").value(id))
                .andExpect(jsonPath("$.properties[?(@.external_mapping_id=='SAFETY_STOCK')].current_value").value("55"));

    }

    @Test
    @DisplayName("get country with specific property set")
    void getWithPropertySet() throws Exception {
        final String isoCode = "US";
        Integer propertySetId = getExpiredPropertySet();

        mockMvc.perform(get(BASE_URL + "/" + isoCode)
                        .param("property_set", String.valueOf(propertySetId)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.iso_code").value(isoCode))
                .andExpect(jsonPath("$.properties[?(@.external_mapping_id=='SAFETY_STOCK')].current_value").value("60"));
    }

    @Test
    @DisplayName("get country with invalid property set should return bad request")
    void getWithBadPropertySet() throws Exception {
        final String isoCode = "US";
        Integer propertySetId = 999999;

        mockMvc.perform(get(BASE_URL + "/" + isoCode)
                        .param("property_set", String.valueOf(propertySetId)))
                .andExpect(status().isBadRequest());
    }

    private Integer getExpiredPropertySet() {
        return jdbcTemplate.queryForObject(
                "SELECT id FROM property_set WHERE state = 'EXPIRED' LIMIT 1",
                Integer.class);
    }

    @Test
    @DisplayName("get country with invalid ID should return bad request")
    void getCountryWithBadId() throws Exception {
        mockMvc.perform(get(BASE_URL + "/999"))
                .andExpect(status().isBadRequest());
    }

    @Test
    @DisplayName("get country with invalid ISO code should return bad request")
    void getCountryWithBadIsoCode() throws Exception {
        mockMvc.perform(get(BASE_URL + "/XY"))
                .andExpect(status().isBadRequest());
    }

    @Test
    @DisplayName("get countries with invalid pagination parameters should return bad request")
    void countriesPaginationWithBadParams() throws Exception {
        mockMvc.perform(get(BASE_URL + "/")
                        .param("limit", "-1")
                        .param("page", "-1"))
                .andExpect(status().isBadRequest());
    }

    @Test
    @DisplayName("get countries with large pagination limit should return all countries")
    void countriesPaginationWithLargeParams() throws Exception {
        mockMvc.perform(get(BASE_URL + "/")
                        .param("limit", "1000"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(250)));
    }

    @Test
    @DisplayName("get countries with SQL injection attempt should be safe")
    void countriesSqlInjection() throws Exception {
        String maliciousFilter = "'; DROP TABLE country; --";

        mockMvc.perform(get(BASE_URL + "/")
                        .param("filter", maliciousFilter))
                .andExpect(status().isOk());

        Integer count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM country", Integer.class);
        assertThat(count).isEqualTo(250);
    }

}
