import 'package:intl/intl.dart'; import '../models/subscription_model.dart'; import '../utils/billing_cost_util.dart'; import 'exchange_rate_service.dart'; import 'cache_manager.dart'; /// 통화 단위 변환 및 포맷팅을 위한 유틸리티 클래스 class CurrencyUtil { static final ExchangeRateService _exchangeRateService = ExchangeRateService(); static final SimpleCacheManager _fmtCache = SimpleCacheManager( maxEntries: 256, maxBytes: 256 * 1024, ttl: const Duration(minutes: 15), ); /// 언어에 따른 기본 통화 반환 static String getDefaultCurrency(String locale) { switch (locale) { case 'ko': return 'KRW'; case 'ja': return 'JPY'; case 'zh': return 'CNY'; case 'en': default: return 'USD'; } } /// 언어에 따른 서브 통화 반환 (영어 제외 모두 USD) static String? getSecondaryCurrency(String locale, String? selectedCurrency) { if (locale == 'en' && selectedCurrency == 'KRW') { return 'KRW'; } return locale != 'en' ? 'USD' : null; } /// 통화 기호 반환 static String getCurrencySymbol(String currency) { switch (currency) { case 'KRW': return '₩'; case 'USD': return '\$'; case 'JPY': return '¥'; case 'CNY': return '¥'; default: return currency; } } /// 통화별 locale 반환 static String _getLocaleForCurrency(String currency) { switch (currency) { case 'KRW': return 'ko_KR'; case 'USD': return 'en_US'; case 'JPY': return 'ja_JP'; case 'CNY': return 'zh_CN'; default: return 'en_US'; } } /// 단일 통화 포맷팅 static String _formatSingleCurrency(double amount, String currency) { final locale = _getLocaleForCurrency(currency); final symbol = getCurrencySymbol(currency); final decimals = (currency == 'KRW' || currency == 'JPY') ? 0 : 2; return NumberFormat.currency( locale: locale, symbol: symbol, decimalDigits: decimals, ).format(amount); } /// 금액 포맷팅 (기본 통화 + 서브 통화) static Future formatAmountWithLocale( double amount, String currency, String locale, ) async { // 캐시 조회 final decimals = (currency == 'KRW' || currency == 'JPY') ? 0 : 2; final key = 'fmt:$locale:$currency:${amount.toStringAsFixed(decimals)}'; final cached = _fmtCache.get(key); if (cached != null) return cached; final defaultCurrency = getDefaultCurrency(locale); // 입력 통화가 기본 통화인 경우 if (currency == defaultCurrency) { final result = _formatSingleCurrency(amount, currency); _fmtCache.set(key, result, size: result.length); return result; } // USD 입력인 경우 - 기본 통화로 변환하여 표시 if (currency == 'USD' && defaultCurrency != 'USD') { final convertedAmount = await _exchangeRateService.convertUsdToTarget( amount, defaultCurrency); if (convertedAmount != null) { final primaryFormatted = _formatSingleCurrency(convertedAmount, defaultCurrency); final usdFormatted = _formatSingleCurrency(amount, 'USD'); final result = '$primaryFormatted ($usdFormatted)'; _fmtCache.set(key, result, size: result.length); return result; } } // 영어 사용자가 KRW 선택한 경우 if (locale == 'en' && currency == 'KRW') { final result = _formatSingleCurrency(amount, currency); _fmtCache.set(key, result, size: result.length); return result; } // 기타 통화 입력인 경우 final result = _formatSingleCurrency(amount, currency); _fmtCache.set(key, result, size: result.length); return result; } /// 구독 목록의 이번 달 총 비용을 계산 (언어별 기본 통화로) /// 이번 달에 결제가 발생하는 구독만 포함하며, 실제 결제 금액을 사용 static Future calculateTotalMonthlyExpenseInDefaultCurrency( List subscriptions, String locale, ) async { final defaultCurrency = getDefaultCurrency(locale); double total = 0.0; final now = DateTime.now(); final currentYear = now.year; final currentMonth = now.month; for (var subscription in subscriptions) { // 이번 달에 결제가 발생하는지 확인 final hasBilling = BillingCostUtil.hasBillingInMonth( subscription.nextBillingDate, subscription.billingCycle, currentYear, currentMonth, ); if (!hasBilling) continue; // 실제 결제 금액으로 역변환 final actualPrice = BillingCostUtil.convertFromMonthlyCost( subscription.currentPrice, subscription.billingCycle, ); final converted = await _exchangeRateService.convertBetweenCurrencies( actualPrice, subscription.currency, defaultCurrency, ); total += converted ?? actualPrice; } return total; } /// 구독 목록의 총 월 비용을 계산 (원화로 환산, 이벤트 가격 반영) - 기존 호환성 유지 static Future calculateTotalMonthlyExpense( List subscriptions) async { return calculateTotalMonthlyExpenseInDefaultCurrency(subscriptions, 'ko'); } /// 구독 목록의 예상 연간 총 비용을 계산 (언어별 기본 통화로) /// 모든 구독의 연간 비용을 합산 (월 환산 비용 × 12) static Future calculateTotalAnnualExpenseInDefaultCurrency( List subscriptions, String locale, ) async { final defaultCurrency = getDefaultCurrency(locale); double total = 0.0; for (var subscription in subscriptions) { // 월 환산 비용 × 12 = 연간 비용 final annualPrice = subscription.currentPrice * 12; final converted = await _exchangeRateService.convertBetweenCurrencies( annualPrice, subscription.currency, defaultCurrency, ); total += converted ?? annualPrice; } return total; } /// 구독의 월 비용을 표시 형식에 맞게 변환 (언어별 통화) static Future formatSubscriptionAmountWithLocale( SubscriptionModel subscription, String locale) async { // 월 환산 금액을 실제 결제 금액으로 역변환 final price = BillingCostUtil.convertFromMonthlyCost( subscription.currentPrice, subscription.billingCycle, ); // 구독 단위 캐시 키 (통화/가격/locale + id + billingCycle) final decimals = (subscription.currency == 'KRW' || subscription.currency == 'JPY') ? 0 : 2; final key = 'subfmt:$locale:${subscription.currency}:${price.toStringAsFixed(decimals)}:${subscription.id}:${subscription.billingCycle}'; final cached = _fmtCache.get(key); if (cached != null) return cached; final result = await formatAmountWithLocale(price, subscription.currency, locale); _fmtCache.set(key, result, size: result.length); return result; } /// 구독의 월 비용을 표시 형식에 맞게 변환 (원화 변환 포함, 이벤트 가격 반영) - 기존 호환성 유지 static Future formatSubscriptionAmount( SubscriptionModel subscription) async { return formatSubscriptionAmountWithLocale(subscription, 'ko'); } /// 총액을 언어별 기본 통화로 표시 static String formatTotalAmountWithLocale(double amount, String locale) { final defaultCurrency = getDefaultCurrency(locale); return _formatSingleCurrency(amount, defaultCurrency); } /// 총액을 원화로 표시 - 기존 호환성 유지 static String formatTotalAmount(double amount) { return formatTotalAmountWithLocale(amount, 'ko'); } /// 환율 정보 텍스트 가져오기 static Future getExchangeRateInfo() { return _exchangeRateService.getFormattedExchangeRateInfo(); } /// 언어별 환율 정보 텍스트 가져오기 static Future getExchangeRateInfoForLocale(String locale) { return _exchangeRateService.getFormattedExchangeRateInfoForLocale(locale); } /// 이벤트로 인한 총 절약액 계산 (언어별 기본 통화로) static Future calculateTotalEventSavingsInDefaultCurrency( List subscriptions, String locale) async { final defaultCurrency = getDefaultCurrency(locale); double total = 0.0; for (var subscription in subscriptions) { if (subscription.isCurrentlyInEvent) { final savings = subscription.eventSavings; final converted = await _exchangeRateService.convertBetweenCurrencies( savings, subscription.currency, defaultCurrency, ); total += converted ?? savings; } } return total; } /// 이벤트로 인한 총 절약액 계산 (원화로 환산) - 기존 호환성 유지 static Future calculateTotalEventSavings( List subscriptions) async { return calculateTotalEventSavingsInDefaultCurrency(subscriptions, 'ko'); } /// 이벤트 절약액을 표시 형식에 맞게 변환 (언어별) static Future formatEventSavingsWithLocale( SubscriptionModel subscription, String locale) async { if (!subscription.isCurrentlyInEvent) { return ''; } final savings = subscription.eventSavings; return formatAmountWithLocale(savings, subscription.currency, locale); } /// 이벤트 절약액을 표시 형식에 맞게 변환 - 기존 호환성 유지 static Future formatEventSavings( SubscriptionModel subscription) async { return formatEventSavingsWithLocale(subscription, 'ko'); } /// 금액과 통화를 받아 포맷팅하여 반환 (언어별) static Future formatAmountWithCurrencyAndLocale( double amount, String currency, String locale) async { return formatAmountWithLocale(amount, currency, locale); } /// 금액과 통화를 받아 포맷팅하여 반환 - 기존 호환성 유지 static Future formatAmount(double amount, String currency) async { return formatAmountWithCurrencyAndLocale(amount, currency, 'ko'); } }