using System.Text.Json;
using Microsoft.Extensions.Configuration;
using UtopiaCanteenSystem.Data;
using UtopiaCanteenSystem.Models;
using UtopiaCanteenSystem.Services;
namespace UtopiaCanteen.BackendService;
///
/// Backend service configuration: appsettings.json + persisted JSON under LocalApplicationData.
///
public sealed class BackendHostConfigService : IConfigService
{
private readonly IConfiguration _configuration;
private readonly string _persistedPath;
private PersistedConfig _persisted;
public BackendHostConfigService(IConfiguration configuration)
{
_configuration = configuration;
_persistedPath = Path.Combine(DatabasePath.GetAppDataFolder(), "backend-settings.json");
_persisted = LoadPersisted();
MergeFromAppSettingsIfNeeded();
}
public string GetListenUrls() =>
_configuration["ListenUrls"] ?? "http://0.0.0.0:5000";
public string GetSyncApiEndpoint() => _configuration["SyncApiEndpoint"] ?? _persisted.SyncApiEndpoint ?? string.Empty;
public void SetSyncApiEndpoint(string endpoint) { _persisted.SyncApiEndpoint = endpoint ?? string.Empty; SavePersisted(); }
public bool GetSyncServiceEnabled() =>
_configuration.GetValue("SyncServiceEnabled", _persisted.SyncServiceEnabled);
public void SetSyncServiceEnabled(bool enabled) { _persisted.SyncServiceEnabled = enabled; SavePersisted(); }
public bool GetScannerConnected() => false;
public void SetScannerConnected(bool connected) { }
public int GetScanIntervalDays() => 0;
public void SetScanIntervalDays(int value) { }
public int GetScanIntervalHours() => 0;
public void SetScanIntervalHours(int value) { }
public int GetScanIntervalMinutes() => 1;
public void SetScanIntervalMinutes(int value) { }
public int GetScanIntervalSeconds() => 0;
public void SetScanIntervalSeconds(int value) { }
public TimeSpan GetScanInterval() => TimeSpan.FromMinutes(1);
public string GetAdminCardId() => "ADMIN";
public void SetAdminCardId(string cardId) { }
public string GetSiteId() => _persisted.SiteId ?? "02";
public void SetSiteId(string siteId) { _persisted.SiteId = siteId ?? string.Empty; SavePersisted(); }
public void ApplyLocationSiteIdFromAuth(string? locationSiteId) { }
public string GetDeviceId()
{
if (!string.IsNullOrWhiteSpace(_persisted.DeviceId))
return _persisted.DeviceId.Trim();
_persisted.DeviceId = ResolveMachineDeviceId();
SavePersisted();
return _persisted.DeviceId;
}
public void SetDeviceId(string deviceId)
{
_persisted.DeviceId = string.IsNullOrWhiteSpace(deviceId)
? ResolveMachineDeviceId()
: deviceId.Trim();
SavePersisted();
}
public bool GetRememberAdminCredentials() => false;
public void SetRememberAdminCredentials(bool remember) { }
public string GetSavedAdminUsername() => string.Empty;
public void SetSavedAdminUsername(string username) { }
public string GetSavedAdminPassword() => string.Empty;
public void SetSavedAdminPassword(string password) { }
public string GetMySqlConnectionString() =>
FirstNonEmpty(_configuration["MySqlConnectionString"], _persisted.MySqlConnectionString);
public void SetMySqlConnectionString(string connectionString)
{
_persisted.MySqlConnectionString = connectionString ?? string.Empty;
SavePersisted();
}
public string GetHrmsLookupConnectionString() =>
ConfigConnectionHelper.GetHrmsOrProductionConnectionString(
FirstNonEmpty(_configuration["HrmsLookupConnectionString"], _persisted.HrmsLookupConnectionString),
GetMySqlConnectionString());
public void SetHrmsLookupConnectionString(string connectionString)
{
_persisted.HrmsLookupConnectionString = connectionString ?? string.Empty;
SavePersisted();
}
public DateTime? GetLastEmployeeRfidCacheSyncUtc() => _persisted.LastEmployeeRfidCacheSyncUtc;
public void SetLastEmployeeRfidCacheSyncUtc(DateTime utc)
{
_persisted.LastEmployeeRfidCacheSyncUtc = utc;
SavePersisted();
}
public DateTime? GetLastMealMenuCacheSyncUtc() => _persisted.LastMealMenuCacheSyncUtc;
public void SetLastMealMenuCacheSyncUtc(DateTime utc)
{
_persisted.LastMealMenuCacheSyncUtc = utc;
SavePersisted();
}
public AppMode GetAppMode() => AppMode.Server;
public void SetAppMode(AppMode mode) { }
public string GetCentralServerBaseUrl() => string.Empty;
public void SetCentralServerBaseUrl(string url) { }
public string GetBackendBaseUrl() => DeriveLocalhostApiBaseUrl(GetListenUrls());
public string GetLocalServerListenUrls() => GetListenUrls();
public void SetLocalServerListenUrls(string urls) { }
private static string ResolveMachineDeviceId()
{
var machineName = Environment.MachineName;
return string.IsNullOrWhiteSpace(machineName)
? Guid.NewGuid().ToString("N")
: machineName.Trim();
}
private void MergeFromAppSettingsIfNeeded()
{
var mysql = _configuration["MySqlConnectionString"];
if (!string.IsNullOrWhiteSpace(mysql) && string.IsNullOrWhiteSpace(_persisted.MySqlConnectionString))
_persisted.MySqlConnectionString = mysql;
var hrms = _configuration["HrmsLookupConnectionString"];
if (!string.IsNullOrWhiteSpace(hrms) && string.IsNullOrWhiteSpace(_persisted.HrmsLookupConnectionString))
_persisted.HrmsLookupConnectionString = hrms;
SavePersisted();
}
private static string FirstNonEmpty(params string?[] values)
{
foreach (var v in values)
{
if (!string.IsNullOrWhiteSpace(v))
return v!;
}
return string.Empty;
}
private static string DeriveLocalhostApiBaseUrl(string listenUrls)
{
var first = listenUrls
.Split(new[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.FirstOrDefault() ?? "http://0.0.0.0:5000";
first = first.TrimEnd('/');
if (first.Contains("0.0.0.0", StringComparison.Ordinal))
first = first.Replace("0.0.0.0", "localhost", StringComparison.Ordinal);
if (first.Contains('+'))
first = first.Replace("+", "localhost", StringComparison.Ordinal);
return first;
}
private PersistedConfig LoadPersisted()
{
try
{
if (!File.Exists(_persistedPath))
return new PersistedConfig();
var json = File.ReadAllText(_persistedPath);
return JsonSerializer.Deserialize(json) ?? new PersistedConfig();
}
catch
{
return new PersistedConfig();
}
}
private void SavePersisted()
{
try
{
var json = JsonSerializer.Serialize(_persisted, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(_persistedPath, json);
}
catch (Exception ex)
{
Logger.Log(ex, "BackendHostConfigService.SavePersisted");
}
}
private sealed class PersistedConfig
{
public string? SyncApiEndpoint { get; set; }
public bool SyncServiceEnabled { get; set; } = true;
public string? MySqlConnectionString { get; set; }
public string? HrmsLookupConnectionString { get; set; }
public DateTime? LastEmployeeRfidCacheSyncUtc { get; set; }
public DateTime? LastMealMenuCacheSyncUtc { get; set; }
public string? SiteId { get; set; }
public string? DeviceId { get; set; }
}
}