using System.Globalization;
using SVSim.Database.Models.Config;
using SVSim.Database.Services;
namespace SVSim.EmulatedEntrypoint.Services;
///
/// Reset-boundary primitive. All timestamps are UTC. The boundary hour is a config
/// value (); everything else — daily
/// reset checks, bucket keys for ViewerEventCounters — derives from it.
/// Because bucket keys and share the same
/// math, they can never disagree.
///
public interface IGameCalendarService
{
///
/// True if a reset boundary has passed since .
/// Null → true (never checked in).
///
bool ResetReady(DateTime? lastCheckpointUtc);
/// Bucket key for the current daily reset window: "day:yyyy-MM-dd".
string DayKey(DateTimeOffset when);
/// Bucket key for the ISO week containing the current daily window's start.
string WeekKey(DateTimeOffset when);
/// Bucket key for the calendar month containing the current daily window's start.
string MonthKey(DateTimeOffset when);
/// Returns [day, week, month, all-time] for the given instant.
IReadOnlyList AllPeriods(DateTimeOffset when);
}
public static class GameCalendarPeriods
{
/// The lifetime bucket string — same constant regardless of reset boundary config.
public const string AllTime = "all-time";
}
public class GameCalendarService : IGameCalendarService
{
private readonly IGameConfigService _config;
public GameCalendarService(IGameConfigService config)
{
_config = config;
}
public bool ResetReady(DateTime? lastCheckpointUtc)
{
if (lastCheckpointUtc is null) return true;
var now = DateTime.UtcNow;
var boundary = MostRecentBoundary(now);
return DateTime.SpecifyKind(lastCheckpointUtc.Value, DateTimeKind.Utc) < boundary;
}
public string DayKey(DateTimeOffset when)
{
var d = WindowStartDate(when);
return $"day:{d:yyyy-MM-dd}";
}
public string WeekKey(DateTimeOffset when)
{
var d = WindowStartDate(when);
var iso = ISOWeek.GetWeekOfYear(d);
var year = ISOWeek.GetYear(d);
return $"week:{year:D4}-W{iso:D2}";
}
public string MonthKey(DateTimeOffset when)
{
var d = WindowStartDate(when);
return $"month:{d:yyyy-MM}";
}
public IReadOnlyList AllPeriods(DateTimeOffset when) => new[]
{
DayKey(when), WeekKey(when), MonthKey(when), GameCalendarPeriods.AllTime,
};
private DateTime MostRecentBoundary(DateTime nowUtc)
{
int hour = _config.Get().DailyResetUtcHour;
var todayBoundary = nowUtc.Date.AddHours(hour);
return nowUtc >= todayBoundary ? todayBoundary : todayBoundary.AddDays(-1);
}
private DateTime WindowStartDate(DateTimeOffset when) =>
MostRecentBoundary(when.UtcDateTime);
}