package com.flightticket.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.flightticket.dto.billing.PricingConfigRequest;
import com.flightticket.model.entity.PricingConfig;
import com.flightticket.model.enums.RequestType;
import com.flightticket.service.PricingConfigService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
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.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;

import java.math.BigDecimal;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

import static org.hamcrest.Matchers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

/**
 * Unit tests for PricingConfigController.
 * Tests REST endpoints for pricing configuration management.
 * 
 * Requirements: 2.1, 2.2, 2.5, 2.8, 8.1
 */
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
class PricingConfigControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    @MockBean
    private PricingConfigService pricingConfigService;
    
    private UUID testClientId;
    private PricingConfigRequest validRequest;
    private PricingConfig testConfig;
    
    @BeforeEach
    void setUp() {
        testClientId = UUID.randomUUID();
        
        validRequest = new PricingConfigRequest();
        validRequest.setRequestType(RequestType.FLIGHT_SEARCH);
        validRequest.setPrice(new BigDecimal("0.002"));
        validRequest.setCurrency("USD");
        
        testConfig = createPricingConfig(null, RequestType.FLIGHT_SEARCH, new BigDecimal("0.002"), "USD");
    }
    
    // ========== POST /api/admin/pricing/global Tests ==========
    
    @Test
    @WithMockUser(roles = "SUPER_ADMIN")
    void shouldSetGlobalPriceSuccessfully() throws Exception {
        // Given
        when(pricingConfigService.setGlobalPrice(
                eq(RequestType.FLIGHT_SEARCH),
                eq(new BigDecimal("0.002")),
                eq("USD")
        )).thenReturn(testConfig);
        
        // When & Then
        mockMvc.perform(post("/api/admin/pricing/global")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(validRequest)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id").exists())
                .andExpect(jsonPath("$.clientId").isEmpty())
                .andExpect(jsonPath("$.requestType").value("FLIGHT_SEARCH"))
                .andExpect(jsonPath("$.unitPrice").value(0.002))
                .andExpect(jsonPath("$.currency").value("USD"));
        
        verify(pricingConfigService).setGlobalPrice(
                RequestType.FLIGHT_SEARCH,
                new BigDecimal("0.002"),
                "USD"
        );
    }
    
    @Test
    @WithMockUser(roles = "SUPER_ADMIN")
    void shouldRejectGlobalPriceWithNegativeValue() throws Exception {
        // Given
        PricingConfigRequest invalidRequest = new PricingConfigRequest();
        invalidRequest.setRequestType(RequestType.FLIGHT_SEARCH);
        invalidRequest.setPrice(new BigDecimal("-0.002"));
        invalidRequest.setCurrency("USD");
        
        // When & Then
        mockMvc.perform(post("/api/admin/pricing/global")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(invalidRequest)))
                .andExpect(status().isBadRequest());
        
        verify(pricingConfigService, never()).setGlobalPrice(any(), any(), any());
    }
    
    @Test
    @WithMockUser(roles = "SUPER_ADMIN")
    void shouldRejectGlobalPriceWithZeroValue() throws Exception {
        // Given
        PricingConfigRequest invalidRequest = new PricingConfigRequest();
        invalidRequest.setRequestType(RequestType.FLIGHT_SEARCH);
        invalidRequest.setPrice(BigDecimal.ZERO);
        invalidRequest.setCurrency("USD");
        
        // When & Then
        mockMvc.perform(post("/api/admin/pricing/global")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(invalidRequest)))
                .andExpect(status().isBadRequest());
        
        verify(pricingConfigService, never()).setGlobalPrice(any(), any(), any());
    }
    
    @Test
    @WithMockUser(roles = "SUPER_ADMIN")
    void shouldRejectGlobalPriceWithInvalidCurrency() throws Exception {
        // Given
        PricingConfigRequest invalidRequest = new PricingConfigRequest();
        invalidRequest.setRequestType(RequestType.FLIGHT_SEARCH);
        invalidRequest.setPrice(new BigDecimal("0.002"));
        invalidRequest.setCurrency("US"); // Invalid: only 2 letters
        
        // When & Then
        mockMvc.perform(post("/api/admin/pricing/global")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(invalidRequest)))
                .andExpect(status().isBadRequest());
        
        verify(pricingConfigService, never()).setGlobalPrice(any(), any(), any());
    }
    
    @Test
    @WithMockUser(roles = "SUPER_ADMIN")
    void shouldRejectGlobalPriceWithMissingRequestType() throws Exception {
        // Given
        PricingConfigRequest invalidRequest = new PricingConfigRequest();
        invalidRequest.setRequestType(null);
        invalidRequest.setPrice(new BigDecimal("0.002"));
        invalidRequest.setCurrency("USD");
        
        // When & Then
        mockMvc.perform(post("/api/admin/pricing/global")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(invalidRequest)))
                .andExpect(status().isBadRequest());
        
        verify(pricingConfigService, never()).setGlobalPrice(any(), any(), any());
    }
    
    @Test
    @WithMockUser(roles = "AGENT")
    void shouldRejectGlobalPriceWithoutSuperAdminRole() throws Exception {
        // When & Then
        mockMvc.perform(post("/api/admin/pricing/global")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(validRequest)))
                .andExpect(status().isForbidden());
        
        verify(pricingConfigService, never()).setGlobalPrice(any(), any(), any());
    }
    
    @Test
    void shouldRejectGlobalPriceWithoutAuthentication() throws Exception {
        // When & Then
        mockMvc.perform(post("/api/admin/pricing/global")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(validRequest)))
                .andExpect(status().isUnauthorized());
        
        verify(pricingConfigService, never()).setGlobalPrice(any(), any(), any());
    }
    
    // ========== POST /api/admin/pricing/client/{clientId} Tests ==========
    
    @Test
    @WithMockUser(roles = "SUPER_ADMIN")
    void shouldSetClientPriceSuccessfully() throws Exception {
        // Given
        PricingConfig clientConfig = createPricingConfig(testClientId, RequestType.FLIGHT_SEARCH, new BigDecimal("0.003"), "USD");
        when(pricingConfigService.setClientPrice(
                eq(testClientId),
                eq(RequestType.FLIGHT_SEARCH),
                eq(new BigDecimal("0.002")),
                eq("USD")
        )).thenReturn(clientConfig);
        
        // When & Then
        mockMvc.perform(post("/api/admin/pricing/client/" + testClientId)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(validRequest)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id").exists())
                .andExpect(jsonPath("$.clientId").value(testClientId.toString()))
                .andExpect(jsonPath("$.requestType").value("FLIGHT_SEARCH"))
                .andExpect(jsonPath("$.unitPrice").value(0.003))
                .andExpect(jsonPath("$.currency").value("USD"));
        
        verify(pricingConfigService).setClientPrice(
                testClientId,
                RequestType.FLIGHT_SEARCH,
                new BigDecimal("0.002"),
                "USD"
        );
    }
    
    @Test
    @WithMockUser(roles = "SUPER_ADMIN")
    void shouldRejectClientPriceWithNegativeValue() throws Exception {
        // Given
        PricingConfigRequest invalidRequest = new PricingConfigRequest();
        invalidRequest.setRequestType(RequestType.FLIGHT_SEARCH);
        invalidRequest.setPrice(new BigDecimal("-0.003"));
        invalidRequest.setCurrency("USD");
        
        // When & Then
        mockMvc.perform(post("/api/admin/pricing/client/" + testClientId)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(invalidRequest)))
                .andExpect(status().isBadRequest());
        
        verify(pricingConfigService, never()).setClientPrice(any(), any(), any(), any());
    }
    
    @Test
    @WithMockUser(roles = "AGENT")
    void shouldRejectClientPriceWithoutSuperAdminRole() throws Exception {
        // When & Then
        mockMvc.perform(post("/api/admin/pricing/client/" + testClientId)
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(validRequest)))
                .andExpect(status().isForbidden());
        
        verify(pricingConfigService, never()).setClientPrice(any(), any(), any(), any());
    }
    
    // ========== GET /api/admin/pricing/global Tests ==========
    
    @Test
    @WithMockUser(roles = "SUPER_ADMIN")
    void shouldGetAllGlobalPricesSuccessfully() throws Exception {
        // Given
        List<PricingConfig> globalPrices = Arrays.asList(
                createPricingConfig(null, RequestType.FLIGHT_SEARCH, new BigDecimal("0.002"), "USD"),
                createPricingConfig(null, RequestType.PRICE_CHECK, new BigDecimal("0.01"), "USD"),
                createPricingConfig(null, RequestType.PNR_CREATION, new BigDecimal("2.50"), "USD")
        );
        when(pricingConfigService.getAllGlobalPrices()).thenReturn(globalPrices);
        
        // When & Then
        mockMvc.perform(get("/api/admin/pricing/global"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(3)))
                .andExpect(jsonPath("$[0].clientId").isEmpty())
                .andExpect(jsonPath("$[0].requestType").value("FLIGHT_SEARCH"))
                .andExpect(jsonPath("$[0].unitPrice").value(0.002))
                .andExpect(jsonPath("$[1].requestType").value("PRICE_CHECK"))
                .andExpect(jsonPath("$[1].unitPrice").value(0.01))
                .andExpect(jsonPath("$[2].requestType").value("PNR_CREATION"))
                .andExpect(jsonPath("$[2].unitPrice").value(2.50));
        
        verify(pricingConfigService).getAllGlobalPrices();
    }
    
    @Test
    @WithMockUser(roles = "SUPER_ADMIN")
    void shouldReturnEmptyListWhenNoGlobalPricesExist() throws Exception {
        // Given
        when(pricingConfigService.getAllGlobalPrices()).thenReturn(List.of());
        
        // When & Then
        mockMvc.perform(get("/api/admin/pricing/global"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(0)));
        
        verify(pricingConfigService).getAllGlobalPrices();
    }
    
    @Test
    @WithMockUser(roles = "AGENT")
    void shouldRejectGetGlobalPricesWithoutSuperAdminRole() throws Exception {
        // When & Then
        mockMvc.perform(get("/api/admin/pricing/global"))
                .andExpect(status().isForbidden());
        
        verify(pricingConfigService, never()).getAllGlobalPrices();
    }
    
    // ========== GET /api/admin/pricing/client/{clientId} Tests ==========
    
    @Test
    @WithMockUser(roles = "SUPER_ADMIN")
    void shouldGetClientPricesSuccessfully() throws Exception {
        // Given
        List<PricingConfig> clientPrices = Arrays.asList(
                createPricingConfig(testClientId, RequestType.FLIGHT_SEARCH, new BigDecimal("0.003"), "USD"),
                createPricingConfig(testClientId, RequestType.PRICE_CHECK, new BigDecimal("0.015"), "USD")
        );
        when(pricingConfigService.getClientPrices(testClientId)).thenReturn(clientPrices);
        
        // When & Then
        mockMvc.perform(get("/api/admin/pricing/client/" + testClientId))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(2)))
                .andExpect(jsonPath("$[0].clientId").value(testClientId.toString()))
                .andExpect(jsonPath("$[0].requestType").value("FLIGHT_SEARCH"))
                .andExpect(jsonPath("$[0].unitPrice").value(0.003))
                .andExpect(jsonPath("$[1].clientId").value(testClientId.toString()))
                .andExpect(jsonPath("$[1].requestType").value("PRICE_CHECK"))
                .andExpect(jsonPath("$[1].unitPrice").value(0.015));
        
        verify(pricingConfigService).getClientPrices(testClientId);
    }
    
    @Test
    @WithMockUser(roles = "SUPER_ADMIN")
    void shouldReturnEmptyListWhenNoClientPricesExist() throws Exception {
        // Given
        when(pricingConfigService.getClientPrices(testClientId)).thenReturn(List.of());
        
        // When & Then
        mockMvc.perform(get("/api/admin/pricing/client/" + testClientId))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(0)));
        
        verify(pricingConfigService).getClientPrices(testClientId);
    }
    
    @Test
    @WithMockUser(roles = "AGENT")
    void shouldRejectGetClientPricesWithoutSuperAdminRole() throws Exception {
        // When & Then
        mockMvc.perform(get("/api/admin/pricing/client/" + testClientId))
                .andExpect(status().isForbidden());
        
        verify(pricingConfigService, never()).getClientPrices(any());
    }
    
    // ========== Currency Validation Tests ==========
    
    @Test
    @WithMockUser(roles = "SUPER_ADMIN")
    void shouldAcceptValidCurrencyCodes() throws Exception {
        // Test various valid currency codes
        String[] validCurrencies = {"USD", "EUR", "GBP", "JPY", "CAD", "AUD"};
        
        for (String currency : validCurrencies) {
            PricingConfigRequest request = new PricingConfigRequest();
            request.setRequestType(RequestType.FLIGHT_SEARCH);
            request.setPrice(new BigDecimal("0.002"));
            request.setCurrency(currency);
            
            PricingConfig config = createPricingConfig(null, RequestType.FLIGHT_SEARCH, new BigDecimal("0.002"), currency);
            when(pricingConfigService.setGlobalPrice(any(), any(), eq(currency))).thenReturn(config);
            
            mockMvc.perform(post("/api/admin/pricing/global")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(request)))
                    .andExpect(status().isCreated())
                    .andExpect(jsonPath("$.currency").value(currency));
        }
    }
    
    @Test
    @WithMockUser(roles = "SUPER_ADMIN")
    void shouldRejectInvalidCurrencyCodes() throws Exception {
        // Test various invalid currency codes
        String[] invalidCurrencies = {"US", "USDD", "usd", "123", "U$D"};
        
        for (String currency : invalidCurrencies) {
            PricingConfigRequest request = new PricingConfigRequest();
            request.setRequestType(RequestType.FLIGHT_SEARCH);
            request.setPrice(new BigDecimal("0.002"));
            request.setCurrency(currency);
            
            mockMvc.perform(post("/api/admin/pricing/global")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(request)))
                    .andExpect(status().isBadRequest());
        }
        
        verify(pricingConfigService, never()).setGlobalPrice(any(), any(), any());
    }
    
    // ========== Helper Methods ==========
    
    private PricingConfig createPricingConfig(UUID clientId, RequestType requestType, BigDecimal price, String currency) {
        PricingConfig config = new PricingConfig();
        config.setId(UUID.randomUUID());
        config.setClientId(clientId);
        config.setRequestType(requestType);
        config.setUnitPrice(price);
        config.setCurrency(currency);
        config.setCreatedAt(Instant.now());
        config.setUpdatedAt(Instant.now());
        return config;
    }
}
