package com.flightticket.integration;

import com.flightticket.dto.agency.CreateAgencyRequest;
import com.flightticket.dto.agency.KycDocumentRequest;
import com.flightticket.dto.agency.UpdateAgencyRequest;
import com.flightticket.model.entity.Agency;
import com.flightticket.model.entity.User;
import com.flightticket.model.entity.Wallet;
import com.flightticket.model.enums.AgencyStatus;
import com.flightticket.model.enums.Role;
import com.flightticket.model.enums.SubscriptionType;
import com.flightticket.model.enums.UserStatus;
import com.flightticket.repository.AgencyRepository;
import com.flightticket.repository.UserRepository;
import com.flightticket.repository.WalletRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MvcResult;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

/**
 * Integration tests for agency management and verification workflow
 * Requirements: 3.1, 3.2, 3.3, 3.4, 3.7, 3.8, 3A.1, 3A.2, 3A.3, 3A.4, 3A.10, 10.1
 */
class AgencyManagementIntegrationTest extends BaseIntegrationTest {
    
    @Autowired
    private AgencyRepository agencyRepository;
    
    @Autowired
    private WalletRepository walletRepository;
    
    @Autowired
    private UserRepository userRepository;
    
    private User superAdminUser;
    
    @BeforeEach
    void setupTestData() {
        walletRepository.deleteAll();
        userRepository.deleteAll();
        agencyRepository.deleteAll();
        
        // Create super admin user
        superAdminUser = new User();
        superAdminUser.setId(UUID.randomUUID());
        superAdminUser.setUsername("superadmin");
        superAdminUser.setEmail("superadmin@platform.com");
        superAdminUser.setRoles(Set.of(Role.SUPER_ADMIN));
        superAdminUser.setStatus(UserStatus.ACTIVE);
        superAdminUser.setCreatedAt(LocalDateTime.now());
        superAdminUser = userRepository.save(superAdminUser);
    }
    
    @Test
    @WithMockUser(username = "superadmin", roles = {"SUPER_ADMIN"})
    void shouldCreateAgencyWithWalletInitialization() throws Exception {
        // Given
        CreateAgencyRequest request = new CreateAgencyRequest();
        request.setName("New Test Agency");
        request.setMatriculeNumber("AGENCY001");
        request.setContactEmail("agency@test.com");
        request.setContactPhone("+1234567890");
        request.setSubscriptionType(SubscriptionType.PAY_AS_YOU_GO);
        request.setMarkupPercentage(BigDecimal.valueOf(15.0));
        
        KycDocumentRequest kycDoc = new KycDocumentRequest();
        kycDoc.setDocumentType("BUSINESS_LICENSE");
        kycDoc.setDocumentUrl("https://example.com/doc.pdf");
        request.setKycDocuments(List.of(kycDoc));
        
        // When
        MvcResult result = mockMvc.perform(post("/api/v1/agencies")
                .contentType(MediaType.APPLICATION_JSON)
                .content(asJsonString(request)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id").exists())
                .andExpect(jsonPath("$.name").value("New Test Agency"))
                .andExpect(jsonPath("$.matriculeNumber").value("AGENCY001"))
                .andExpect(jsonPath("$.status").value("PENDING_VERIFICATION"))
                .andExpect(jsonPath("$.isVerified").value(false))
                .andExpect(jsonPath("$.subscriptionType").value("MONTHLY"))
                .andExpect(jsonPath("$.markupPercentage").value(15.0))
                .andReturn();
        
        // Then - Verify wallet was created
        String responseBody = result.getResponse().getContentAsString();
        UUID agencyId = UUID.fromString(objectMapper.readTree(responseBody).get("id").asText());
        
        Wallet wallet = walletRepository.findByAgencyId(agencyId).orElse(null);
        assertThat(wallet).isNotNull();
        assertThat(wallet.getBalance()).isEqualByComparingTo(BigDecimal.ZERO);
        assertThat(wallet.getReservedAmount()).isEqualByComparingTo(BigDecimal.ZERO);
    }
    
    @Test
    @WithMockUser(username = "superadmin", roles = {"SUPER_ADMIN"})
    void shouldRejectAgencyCreationWithoutKycDocuments() throws Exception {
        // Given
        CreateAgencyRequest request = new CreateAgencyRequest();
        request.setName("Invalid Agency");
        request.setMatriculeNumber("INVALID001");
        request.setContactEmail("invalid@test.com");
        request.setContactPhone("+1234567890");
        request.setSubscriptionType(SubscriptionType.PAY_AS_YOU_GO);
        request.setMarkupPercentage(BigDecimal.valueOf(10.0));
        request.setKycDocuments(List.of()); // Empty KYC documents
        
        // When & Then
        mockMvc.perform(post("/api/v1/agencies")
                .contentType(MediaType.APPLICATION_JSON)
                .content(asJsonString(request)))
                .andExpect(status().isBadRequest());
    }
    
    @Test
    @WithMockUser(username = "superadmin", roles = {"SUPER_ADMIN"})
    void shouldRejectDuplicateMatriculeNumber() throws Exception {
        // Given - Create first agency
        Agency existingAgency = new Agency();
        existingAgency.setId(UUID.randomUUID());
        existingAgency.setName("Existing Agency");
        existingAgency.setMatriculeNumber("DUPLICATE001");
        existingAgency.setContactEmail("existing@test.com");
        existingAgency.setContactPhone("+1234567890");
        existingAgency.setStatus(AgencyStatus.ACTIVE);
        existingAgency.setIsVerified(true);
        existingAgency.setSubscriptionType(SubscriptionType.PAY_AS_YOU_GO);
        existingAgency.setSubscriptionStartDate(LocalDate.now());
        existingAgency.setMarkupPercentage(BigDecimal.valueOf(10.0));
        existingAgency.setCreatedAt(LocalDateTime.now());
        agencyRepository.save(existingAgency);
        
        // When - Try to create agency with same matricule number
        CreateAgencyRequest request = new CreateAgencyRequest();
        request.setName("New Agency");
        request.setMatriculeNumber("DUPLICATE001");
        request.setContactEmail("new@test.com");
        request.setContactPhone("+9876543210");
        request.setSubscriptionType(SubscriptionType.SUBSCRIPTION);
        request.setMarkupPercentage(BigDecimal.valueOf(12.0));
        
        KycDocumentRequest kycDoc = new KycDocumentRequest();
        kycDoc.setDocumentType("BUSINESS_LICENSE");
        kycDoc.setDocumentUrl("https://example.com/doc.pdf");
        request.setKycDocuments(List.of(kycDoc));
        
        // Then
        mockMvc.perform(post("/api/v1/agencies")
                .contentType(MediaType.APPLICATION_JSON)
                .content(asJsonString(request)))
                .andExpect(status().isConflict());
    }
    
    @Test
    @WithMockUser(username = "superadmin", roles = {"SUPER_ADMIN"})
    void shouldVerifyAgencySuccessfully() throws Exception {
        // Given - Create unverified agency
        Agency agency = new Agency();
        agency.setId(UUID.randomUUID());
        agency.setName("Unverified Agency");
        agency.setMatriculeNumber("UNVERIFIED001");
        agency.setContactEmail("unverified@test.com");
        agency.setContactPhone("+1234567890");
        agency.setStatus(AgencyStatus.PENDING_VERIFICATION);
        agency.setIsVerified(false);
        agency.setSubscriptionType(SubscriptionType.PAY_AS_YOU_GO);
        agency.setSubscriptionStartDate(LocalDate.now());
        agency.setMarkupPercentage(BigDecimal.valueOf(10.0));
        agency.setCreatedAt(LocalDateTime.now());
        agency = agencyRepository.save(agency);
        
        // When
        mockMvc.perform(post("/api/v1/agencies/" + agency.getId() + "/verify"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.isVerified").value(true))
                .andExpect(jsonPath("$.status").value("ACTIVE"))
                .andExpect(jsonPath("$.verifiedAt").exists());
        
        // Then - Verify in database
        Agency verifiedAgency = agencyRepository.findById(agency.getId()).orElseThrow();
        assertThat(verifiedAgency.getIsVerified()).isTrue();
        assertThat(verifiedAgency.getStatus()).isEqualTo(AgencyStatus.ACTIVE);
        assertThat(verifiedAgency.getVerifiedAt()).isNotNull();
        assertThat(verifiedAgency.getVerifiedBy()).isNotNull();
    }
    
    @Test
    @WithMockUser(username = "superadmin", roles = {"SUPER_ADMIN"})
    void shouldGetPendingVerificationAgencies() throws Exception {
        // Given - Create multiple agencies with different statuses
        Agency pendingAgency1 = createAgency("PENDING001", AgencyStatus.PENDING_VERIFICATION, false);
        Agency pendingAgency2 = createAgency("PENDING002", AgencyStatus.PENDING_VERIFICATION, false);
        Agency activeAgency = createAgency("ACTIVE001", AgencyStatus.ACTIVE, true);
        
        agencyRepository.saveAll(List.of(pendingAgency1, pendingAgency2, activeAgency));
        
        // When & Then
        mockMvc.perform(get("/api/v1/agencies/pending-verification"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.length()").value(2))
                .andExpect(jsonPath("$[*].status").value(org.hamcrest.Matchers.everyItem(
                        org.hamcrest.Matchers.is("PENDING_VERIFICATION"))));
    }
    
    @Test
    @WithMockUser(username = "superadmin", roles = {"SUPER_ADMIN"})
    void shouldManuallyActivateAgency() throws Exception {
        // Given
        Agency agency = createAgency("INACTIVE001", AgencyStatus.INACTIVE, true);
        agency = agencyRepository.save(agency);
        
        // When
        mockMvc.perform(post("/api/v1/agencies/" + agency.getId() + "/activate"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.status").value("ACTIVE"));
        
        // Then
        Agency activatedAgency = agencyRepository.findById(agency.getId()).orElseThrow();
        assertThat(activatedAgency.getStatus()).isEqualTo(AgencyStatus.ACTIVE);
    }
    
    @Test
    @WithMockUser(username = "superadmin", roles = {"SUPER_ADMIN"})
    void shouldManuallyDeactivateAgencyWithReason() throws Exception {
        // Given
        Agency agency = createAgency("ACTIVE002", AgencyStatus.ACTIVE, true);
        agency = agencyRepository.save(agency);
        
        String deactivationReason = "Non-payment of subscription fees";
        
        // When
        mockMvc.perform(post("/api/v1/agencies/" + agency.getId() + "/deactivate")
                .param("reason", deactivationReason))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.status").value("INACTIVE"))
                .andExpect(jsonPath("$.deactivationReason").value(deactivationReason));
        
        // Then
        Agency deactivatedAgency = agencyRepository.findById(agency.getId()).orElseThrow();
        assertThat(deactivatedAgency.getStatus()).isEqualTo(AgencyStatus.INACTIVE);
        assertThat(deactivatedAgency.getDeactivationReason()).isEqualTo(deactivationReason);
        assertThat(deactivatedAgency.getDeactivatedAt()).isNotNull();
        assertThat(deactivatedAgency.getDeactivatedBy()).isNotNull();
    }
    
    @Test
    @WithMockUser(username = "superadmin", roles = {"SUPER_ADMIN"})
    void shouldUpdateAgencyMarkupPercentage() throws Exception {
        // Given
        Agency agency = createAgency("MARKUP001", AgencyStatus.ACTIVE, true);
        agency.setMarkupPercentage(BigDecimal.valueOf(10.0));
        agency = agencyRepository.save(agency);
        
        BigDecimal newMarkup = BigDecimal.valueOf(15.5);
        
        // When
        mockMvc.perform(put("/api/v1/agencies/" + agency.getId() + "/markup")
                .param("markupPercentage", newMarkup.toString()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.markupPercentage").value(15.5));
        
        // Then
        Agency updatedAgency = agencyRepository.findById(agency.getId()).orElseThrow();
        assertThat(updatedAgency.getMarkupPercentage()).isEqualByComparingTo(newMarkup);
    }
    
    @Test
    @WithMockUser(username = "superadmin", roles = {"SUPER_ADMIN"})
    void shouldRecordPaymentAndUpdateNextDueDate() throws Exception {
        // Given
        Agency agency = createAgency("PAYMENT001", AgencyStatus.ACTIVE, true);
        agency.setSubscriptionType(SubscriptionType.PAY_AS_YOU_GO);
        agency.setSubscriptionStartDate(LocalDate.now().minusMonths(1));
        agency.setNextPaymentDueDate(LocalDate.now());
        agency = agencyRepository.save(agency);
        
        LocalDate paymentDate = LocalDate.now();
        
        // When
        mockMvc.perform(post("/api/v1/agencies/" + agency.getId() + "/payment")
                .param("paymentDate", paymentDate.toString()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.lastPaymentDate").value(paymentDate.toString()));
        
        // Then
        Agency updatedAgency = agencyRepository.findById(agency.getId()).orElseThrow();
        assertThat(updatedAgency.getLastPaymentDate()).isEqualTo(paymentDate);
        assertThat(updatedAgency.getNextPaymentDueDate()).isAfter(paymentDate);
    }
    
    @Test
    @WithMockUser(username = "superadmin", roles = {"SUPER_ADMIN"})
    void shouldRenewSubscription() throws Exception {
        // Given
        Agency agency = createAgency("RENEW001", AgencyStatus.ACTIVE, true);
        agency.setSubscriptionType(SubscriptionType.PAY_AS_YOU_GO);
        agency = agencyRepository.save(agency);
        
        // When
        mockMvc.perform(post("/api/v1/agencies/" + agency.getId() + "/renew")
                .param("subscriptionType", "ANNUAL"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.subscriptionType").value("ANNUAL"));
        
        // Then
        Agency renewedAgency = agencyRepository.findById(agency.getId()).orElseThrow();
        assertThat(renewedAgency.getSubscriptionType()).isEqualTo(SubscriptionType.SUBSCRIPTION);
    }
    
    @Test
    @WithMockUser(username = "superadmin", roles = {"SUPER_ADMIN"})
    void shouldGetAgenciesNearingExpiration() throws Exception {
        // Given - Create agencies with different expiration dates
        Agency expiringAgency = createAgency("EXPIRING001", AgencyStatus.ACTIVE, true);
        expiringAgency.setNextPaymentDueDate(LocalDate.now().plusDays(5));
        
        Agency notExpiringAgency = createAgency("NOTEXPIRING001", AgencyStatus.ACTIVE, true);
        notExpiringAgency.setNextPaymentDueDate(LocalDate.now().plusDays(30));
        
        agencyRepository.saveAll(List.of(expiringAgency, notExpiringAgency));
        
        // When & Then
        mockMvc.perform(get("/api/v1/agencies/expiring")
                .param("daysThreshold", "7"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.length()").value(1))
                .andExpect(jsonPath("$[0].matriculeNumber").value("EXPIRING001"));
    }
    
    private Agency createAgency(String matriculeNumber, AgencyStatus status, boolean isVerified) {
        Agency agency = new Agency();
        agency.setId(UUID.randomUUID());
        agency.setName("Agency " + matriculeNumber);
        agency.setMatriculeNumber(matriculeNumber);
        agency.setContactEmail(matriculeNumber.toLowerCase() + "@test.com");
        agency.setContactPhone("+1234567890");
        agency.setStatus(status);
        agency.setIsVerified(isVerified);
        agency.setSubscriptionType(SubscriptionType.PAY_AS_YOU_GO);
        agency.setSubscriptionStartDate(LocalDate.now());
        agency.setNextPaymentDueDate(LocalDate.now().plusMonths(1));
        agency.setMarkupPercentage(BigDecimal.valueOf(10.0));
        agency.setCreatedAt(LocalDateTime.now());
        return agency;
    }
}
