package de.avatic.lcc.controller.configuration;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
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.http.MediaType;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

/**
 * Advanced integration tests for RateController covering edge cases and complex scenarios
 */
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
class RateControllerAdvancedIntegrationTest {

    @Autowired
    private MockMvc mockMvc;

    private static final String BASE_URL = "/api/rates";

    // Parameterized Tests

    @ParameterizedTest
    @ValueSource(strings = {"container", "matrix"})
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
    void testRateEndpoints_WithDifferentLimits(String endpoint) throws Exception {
        int[] limits = {1, 5, 10, 20, 50};

        for (int limit : limits) {
            mockMvc.perform(get(BASE_URL + "/" + endpoint)
                            .param("limit", String.valueOf(limit))
                            .contentType(MediaType.APPLICATION_JSON))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$", hasSize(lessThanOrEqualTo(limit))));
        }
    }

    @ParameterizedTest
    @CsvSource({
            "container,RAIL",
            "container,SEA",
            "container,ROAD",
            "container,POST_RUN"
    })
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
    void testContainerRates_FilterByTransportType(String endpoint, String transportType) throws Exception {
        mockMvc.perform(get(BASE_URL + "/" + endpoint)
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[?(@.type == '" + transportType + "')]").exists());
    }

    // Complex Date Range Tests

    @Test
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
    void testContainerRates_WithFutureDateFilter_ShouldReturnOnlyFutureValidRates() throws Exception {
        LocalDateTime futureDate = LocalDateTime.now().plusMonths(6);
        String formattedDate = futureDate.format(DateTimeFormatter.ISO_DATE_TIME);

        mockMvc.perform(get(BASE_URL + "/container")
                        .param("validAt", formattedDate)
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[*].validity_period.state", everyItem(equalTo("VALID"))));
    }

    @Test
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
    void testContainerRates_WithPastDateFilter_ShouldReturnHistoricalRates() throws Exception {
        LocalDateTime pastDate = LocalDateTime.now().minusMonths(18);
        String formattedDate = pastDate.format(DateTimeFormatter.ISO_DATE_TIME);

        mockMvc.perform(get(BASE_URL + "/container")
                        .param("validAt", formattedDate)
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk());
    }

    // Concurrent Request Tests

    @Test
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
    void testContainerRates_MultipleFiltersSimultaneously_ShouldPrioritizeValidAt() throws Exception {
        LocalDateTime validAt = LocalDateTime.now();
        String formattedDate = validAt.format(DateTimeFormatter.ISO_DATE_TIME);

        // When both validAt and valid parameters are provided, validAt should take precedence
        mockMvc.perform(get(BASE_URL + "/container")
                        .param("validAt", formattedDate)
                        .param("valid", "2") // This should be ignored
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk());
    }

    // State Transition Tests

    @Test
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
    void testValidityPeriod_StateTransitions_DraftToValid() throws Exception {
        // First check if there are draft rates
        mockMvc.perform(get(BASE_URL + "/staged_changes")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$").value(true));

        // Approve drafts
        mockMvc.perform(put(BASE_URL + "/staged_changes")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk());

        // Verify no more drafts exist
        mockMvc.perform(get(BASE_URL + "/staged_changes")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$").value(false));
    }

    // Edge Cases for Pagination

    @Test
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
    void testPagination_RequestingPageBeyondAvailable_ShouldReturnEmptyList() throws Exception {
        mockMvc.perform(get(BASE_URL + "/container")
                        .param("limit", "100")
                        .param("page", "999")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(0)))
                .andExpect(header().string("X-Current-Page", "999"));
    }

    @Test
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
    void testPagination_WithZeroLimit_ShouldReturnBadRequest() throws Exception {
        mockMvc.perform(get(BASE_URL + "/container")
                        .param("limit", "0")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isBadRequest());
    }

    // Complex Filtering Scenarios

    @Test
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
    void testMatrixRates_FilterBySpecificCountryPairs() throws Exception {
        mockMvc.perform(get(BASE_URL + "/matrix")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[?(@.origin.country.iso_code == 'DE' && @.destination.country.iso_code == 'FR')]").exists());
    }

    // Business Logic Validation Tests

    @Test
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
    void testContainerRates_ValidateRateHierarchy() throws Exception {
        mockMvc.perform(get(BASE_URL + "/container")
                        .param("limit", "100")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                // 40ft container rate should be less than 40HC rate
                .andExpect(jsonPath("$[*]", everyItem(
                        allOf(
                                hasEntry(equalTo("rates"),
                                        allOf(
                                                hasKey("FEU"),
                                                hasKey("HQ")
                                        )
                                )
                        )
                )));
    }

    // Null and Empty Value Handling

    @Test
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
    void testValidityPeriods_WithNullEndDates_ShouldHandleGracefully() throws Exception {
        mockMvc.perform(get(BASE_URL + "/periods")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[?(@.end_date == null)]").exists());
    }

    // Special Character Handling

    @Test
    void testInvalidParameters_WithSpecialCharacters_ShouldReturn400() throws Exception {
        mockMvc.perform(get(BASE_URL + "/container")
                        .param("limit", "20'; DROP TABLE container_rate; --")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isBadRequest());
    }

    // Large Dataset Performance Test

    @Test
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
    @Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
    void testPerformance_WithLargeDataset_ShouldMaintainResponseTime() throws Exception {
        long startTime = System.currentTimeMillis();

        // Test with pagination to ensure efficient querying
        mockMvc.perform(get(BASE_URL + "/container")
                        .param("limit", "50")
                        .param("page", "10")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk());

        long endTime = System.currentTimeMillis();
        long duration = endTime - startTime;

        // Even with large dataset, paginated request should be fast
        assert duration < 1000 : "Request took too long with large dataset: " + duration + "ms";
    }

    // Idempotency Tests

//    @Test
//    @Sql(scripts = {"classpath:master_data/reduced_rate_setup.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
//    @Sql(scripts = {"classpath:master_data/reduced_rate_setup-cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
//    void testInvalidatePeriod_MultipleCallsToSameId_ShouldBeIdempotent() throws Exception {
//        Integer periodId = 4; // Using invalid period from test data
//
//        // First call
//        mockMvc.perform(delete(BASE_URL + "/periods/{id}", periodId)
//                        .contentType(MediaType.APPLICATION_JSON))
//                .andExpect(status().isOk());
//
//        // Second call - should still return OK (idempotent)
//        mockMvc.perform(delete(BASE_URL + "/periods/{id}", periodId)
//                        .contentType(MediaType.APPLICATION_JSON))
//                .andExpect(status().isOk());
//    }
}