package com.paymentlink.service; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.paymentlink.model.entity.Region; import com.paymentlink.repository.RegionRepository; import jakarta.annotation.PostConstruct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.util.concurrent.TimeUnit; @Service public class RegionService { private static final Logger logger = LoggerFactory.getLogger(RegionService.class); private final RegionRepository regionRepository; private Cache regionCache; private Cache> enabledRegionsCache; @Value("${region.cache.ttl:600000}") private long cacheTtl; @Value("${region.default.enabled:false}") private boolean defaultEnabled; public RegionService(RegionRepository regionRepository) { this.regionRepository = regionRepository; } @PostConstruct public void init() { // Initialize local cache (10 min TTL) regionCache = Caffeine.newBuilder() .maximumSize(200) .expireAfterWrite(cacheTtl, TimeUnit.MILLISECONDS) .build(); enabledRegionsCache = Caffeine.newBuilder() .maximumSize(1) .expireAfterWrite(cacheTtl, TimeUnit.MILLISECONDS) .build(); logger.info("RegionService initialized with cache TTL: {}ms", cacheTtl); } /** * Check if country is enabled */ public boolean isCountryEnabled(String countryCode) { Region region = getRegion(countryCode); return region != null && region.getEnabled(); } /** * Get region by country code */ public Region getRegion(String countryCode) { if (countryCode == null || countryCode.isEmpty()) { return null; } // Check cache first Region cached = regionCache.getIfPresent(countryCode); if (cached != null) { return cached; } // Fetch from database Optional regionOpt = regionRepository.findByCountryCode(countryCode.toUpperCase()); if (regionOpt.isPresent()) { Region region = regionOpt.get(); regionCache.put(countryCode, region); return region; } return null; } /** * Get all enabled regions */ public List getAllEnabledRegions() { // Check cache first List cached = enabledRegionsCache.getIfPresent("enabled"); if (cached != null) { return cached; } // Fetch from database List enabled = regionRepository.findAllEnabled(); enabledRegionsCache.put("enabled", enabled); return enabled; } /** * Get all regions */ public List getAllRegions() { return regionRepository.findAll(); } /** * Enable a country */ @Transactional public void enableCountry(String countryCode) { Optional regionOpt = regionRepository.findByCountryCode(countryCode.toUpperCase()); if (regionOpt.isPresent()) { Region region = regionOpt.get(); region.setEnabled(true); regionRepository.save(region); // Clear caches regionCache.invalidate(countryCode); enabledRegionsCache.invalidateAll(); logger.info("Enabled country: {}", countryCode); } else { logger.warn("Country not found: {}", countryCode); } } /** * Disable a country */ @Transactional public void disableCountry(String countryCode) { Optional regionOpt = regionRepository.findByCountryCode(countryCode.toUpperCase()); if (regionOpt.isPresent()) { Region region = regionOpt.get(); region.setEnabled(false); regionRepository.save(region); // Clear caches regionCache.invalidate(countryCode); enabledRegionsCache.invalidateAll(); logger.info("Disabled country: {}", countryCode); } else { logger.warn("Country not found: {}", countryCode); } } /** * Get statistics */ public Map getStatistics() { long total = regionRepository.count(); long enabled = regionRepository.countEnabled(); Map stats = new HashMap<>(); stats.put("total", total); stats.put("enabled", enabled); stats.put("disabled", total - enabled); return stats; } /** * Initialize default regions with country data * This method is idempotent and should be called on application startup */ @Transactional public void initializeDefaultRegions() { long existingCount = regionRepository.count(); if (existingCount > 0) { logger.info("Regions table already populated with {} entries", existingCount); return; } logger.info("Initializing default regions..."); List regions = getDefaultRegionData(); regionRepository.saveAll(regions); logger.info("Initialized {} regions", regions.size()); } /** * Get default region data for major countries * In production, this would be loaded from a JSON file or database */ private List getDefaultRegionData() { List regions = new ArrayList<>(); // Major regions to enable by default for testing Set enabledByDefault = Set.of("US", "CA", "GB", "FR", "DE", "ES"); // Add major countries (sample set - in production, load all 195 countries) regions.add(createRegion("US", "United States", "en", "USD", enabledByDefault.contains("US"))); regions.add(createRegion("CA", "Canada", "en", "CAD", enabledByDefault.contains("CA"))); regions.add(createRegion("GB", "United Kingdom", "en", "GBP", enabledByDefault.contains("GB"))); regions.add(createRegion("FR", "France", "fr", "EUR", enabledByDefault.contains("FR"))); regions.add(createRegion("DE", "Germany", "de", "EUR", enabledByDefault.contains("DE"))); regions.add(createRegion("ES", "Spain", "es", "EUR", enabledByDefault.contains("ES"))); regions.add(createRegion("IT", "Italy", "it", "EUR", false)); regions.add(createRegion("MX", "Mexico", "es", "MXN", false)); regions.add(createRegion("BR", "Brazil", "pt", "BRL", false)); regions.add(createRegion("AR", "Argentina", "es", "ARS", false)); regions.add(createRegion("JP", "Japan", "ja", "JPY", false)); regions.add(createRegion("CN", "China", "zh", "CNY", false)); regions.add(createRegion("IN", "India", "en", "INR", false)); regions.add(createRegion("AU", "Australia", "en", "AUD", false)); regions.add(createRegion("NZ", "New Zealand", "en", "NZD", false)); regions.add(createRegion("SG", "Singapore", "en", "SGD", false)); regions.add(createRegion("HK", "Hong Kong", "en", "HKD", false)); regions.add(createRegion("KR", "South Korea", "ko", "KRW", false)); regions.add(createRegion("SE", "Sweden", "sv", "SEK", false)); regions.add(createRegion("NO", "Norway", "no", "NOK", false)); regions.add(createRegion("DK", "Denmark", "da", "DKK", false)); regions.add(createRegion("CH", "Switzerland", "de", "CHF", false)); regions.add(createRegion("NL", "Netherlands", "nl", "EUR", false)); regions.add(createRegion("BE", "Belgium", "nl", "EUR", false)); regions.add(createRegion("AT", "Austria", "de", "EUR", false)); regions.add(createRegion("PL", "Poland", "pl", "PLN", false)); regions.add(createRegion("PT", "Portugal", "pt", "EUR", false)); regions.add(createRegion("IE", "Ireland", "en", "EUR", false)); regions.add(createRegion("TR", "Turkey", "tr", "TRY", false)); regions.add(createRegion("ZA", "South Africa", "en", "ZAR", false)); return regions; } private Region createRegion(String code, String name, String lang, String currency, boolean enabled) { return Region.builder() .countryCode(code) .countryName(name) .languageCode(lang) .currencyCode(currency) .enabled(enabled) .build(); } /** * Clear all caches */ public void clearCache() { regionCache.invalidateAll(); enabledRegionsCache.invalidateAll(); logger.info("Region caches cleared"); } }