defmodule DaProductApp.Settings do @moduledoc """ Settings context for platform configuration management. This context handles platform-wide configuration settings across different categories including general, UPI, international payments, security, notifications, integrations, and system settings. Supports gradual migration from hardcoded to database-backed settings. """ import Ecto.Query, warn: false alias DaProductApp.Repo alias DaProductApp.Settings.PlatformSetting alias DaProductApp.FeatureFlags @doc """ Gets all platform settings organized by category. Uses feature flags to determine data source. """ def get_all_settings do if FeatureFlags.enabled?(:use_database_settings) do get_database_settings() else get_hardcoded_settings() end end @doc """ Gets settings for a specific category. """ def get_settings_by_category(category) do if FeatureFlags.enabled?(:use_database_settings) do get_database_settings_by_category(category) else all_settings = get_hardcoded_settings() Map.get(all_settings, category, %{}) end end @doc """ Updates settings for a specific category. """ def update_settings(category, updates) when is_map(updates) do if FeatureFlags.enabled?(:use_database_settings) do update_database_settings(category, updates) else # In hardcoded mode, simulate success but don't persist {:ok, updates} end end @doc """ Updates a single setting. """ def update_setting(category, key, value, opts \\ []) do value_type = Keyword.get(opts, :type, "string") description = Keyword.get(opts, :description) if FeatureFlags.enabled?(:use_database_settings) do update_database_setting(category, key, value, value_type, description) else # In hardcoded mode, simulate success {:ok, %{category: category, key: key, value: value}} end end @doc """ Gets a single setting value with optional default. """ def get_setting(category, key, default \\ nil) do if FeatureFlags.enabled?(:use_database_settings) do get_database_setting(category, key, default) else settings = get_settings_by_category(category) Map.get(settings, key, default) end end @doc """ Validates a setting value. """ def validate_setting(category, key, value) do case {category, key} do {"general", "max_transaction_amount"} when is_number(value) and value > 0 -> {:ok, value} {"general", "min_transaction_amount"} when is_number(value) and value > 0 -> {:ok, value} {"upi", "npci_timeout"} when is_integer(value) and value > 0 -> {:ok, value} {"upi", "retry_attempts"} when is_integer(value) and value >= 0 -> {:ok, value} {"security", "session_timeout"} when is_integer(value) and value > 0 -> {:ok, value} {"security", "max_login_attempts"} when is_integer(value) and value > 0 -> {:ok, value} _ -> {:ok, value} # Default acceptance for demo end end # Private functions for different setting categories defp get_general_settings do %{ "platform_name" => "Mercury UPI PSP", "platform_description" => "Advanced UPI Payment Service Provider", "default_timezone" => "Asia/Kolkata", "default_currency" => "INR", "maintenance_mode" => false, "debug_mode" => false, "max_transaction_amount" => 200000.00, "min_transaction_amount" => 1.00 } end defp get_upi_settings do %{ "npci_endpoint" => "https://api.npci.org.in/v1", "npci_timeout" => 30, "retry_attempts" => 3, "retry_delay" => 5, "qr_expiry_minutes" => 15, "max_qr_per_merchant" => 100, "validate_vpa_real_time" => true, "allow_zero_amount_qr" => true, "mandate_beneficiary_validation" => true } end defp get_international_settings do %{ "enable_international_payments" => true, "default_fx_provider" => "reuters", "fx_rate_refresh_interval" => 300, "fx_rate_tolerance" => 0.05, "supported_corridors" => ["SGD-INR", "USD-INR", "AED-INR"], "max_international_amount" => 500000.00, "compliance_check_required" => true, "auto_settlement_enabled" => false } end defp get_security_settings do %{ "encryption_enabled" => true, "encryption_algorithm" => "AES-256-GCM", "session_timeout" => 30, "max_login_attempts" => 5, "lockout_duration" => 15, "require_2fa" => false, "ip_whitelist_enabled" => false, "audit_logging_enabled" => true, "sensitive_data_masking" => true } end defp get_notification_settings do %{ "email_enabled" => true, "sms_enabled" => true, "webhook_enabled" => true, "push_notifications_enabled" => false, "transaction_alerts" => true, "failure_alerts" => true, "system_alerts" => true, "daily_reports" => true, "alert_threshold_amount" => 50000.00 } end defp get_integration_settings do %{ "razorpay_enabled" => false, "razorpay_api_key" => "", "razorpay_webhook_secret" => "", "paytm_enabled" => true, "paytm_merchant_id" => "PAYTM_MERCHANT_001", "paytm_api_key" => "", "phonepe_enabled" => true, "phonepe_merchant_id" => "PHONEPE_MERCHANT_001", "phonepe_api_key" => "" } end defp get_system_settings do %{ "max_concurrent_transactions" => 1000, "queue_size_limit" => 10000, "worker_pool_size" => 50, "database_pool_size" => 20, "cache_ttl_seconds" => 3600, "log_level" => "info", "metrics_enabled" => true, "health_check_interval" => 60, "live_reload_enabled" => true, "performance_monitoring" => false, "error_tracking_enabled" => true, "backup_enabled" => true, "backup_frequency" => "daily", "log_retention_days" => 90, "metrics_retention_days" => 30 } end # ================================ # DATABASE-BACKED SETTINGS # ================================ defp get_database_settings do settings = from(s in PlatformSetting, where: s.is_active == true) |> Repo.all() |> Enum.group_by(& &1.category) |> Enum.into(%{}, fn {category, settings} -> settings_map = settings |> Enum.into(%{}, fn setting -> {setting.key, PlatformSetting.parse_value(setting)} end) {category, settings_map} end) # Merge with defaults for any missing categories Map.merge(get_hardcoded_settings(), settings) end defp get_database_settings_by_category(category) do from(s in PlatformSetting, where: s.category == ^category and s.is_active == true) |> Repo.all() |> Enum.into(%{}, fn setting -> {setting.key, PlatformSetting.parse_value(setting)} end) end defp get_database_setting(category, key, default) do case Repo.get_by(PlatformSetting, category: category, key: key, is_active: true) do nil -> default setting -> PlatformSetting.parse_value(setting) end end defp update_database_settings(category, updates) do results = for {key, value} <- updates do update_database_setting(category, key, value, "string", nil) end case Enum.find(results, &match?({:error, _}, &1)) do nil -> {:ok, updates} error -> error end end defp update_database_setting(category, key, value, value_type, description) do case Repo.get_by(PlatformSetting, category: category, key: key) do nil -> # Create new setting %PlatformSetting{} |> PlatformSetting.changeset(%{ category: category, key: key, value: PlatformSetting.encode_value(value, value_type), value_type: value_type, description: description, is_active: true }) |> Repo.insert() existing -> # Update existing setting existing |> PlatformSetting.changeset(%{ value: PlatformSetting.encode_value(value, value_type), value_type: value_type, description: description || existing.description, is_active: true }) |> Repo.update() end end defp get_hardcoded_settings do %{ "general" => get_general_settings(), "upi" => get_upi_settings(), "international" => get_international_settings(), "security" => get_security_settings(), "notifications" => get_notification_settings(), "integrations" => get_integration_settings(), "system" => get_system_settings() } end end