using System.Globalization; using System.IO; using System.Windows; using Marathon.Application; using Marathon.Infrastructure; using Marathon.UI.Services; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Serilog; namespace Marathon.Hosts.WpfBlazor; /// /// WPF application entry-point. Builds an with Serilog, /// configuration (appsettings.json + Local + env vars), and the Marathon UI /// service collection. Composes Application + Infrastructure modules /// optionally — those module entry points may not yet exist while parallel /// Phase 2/3/4 work merges. /// public partial class App : System.Windows.Application { public IHost? Host { get; private set; } /// /// Absolute path to the Local override settings file. Resolved from the /// host's content root (the directory containing appsettings.json). /// public static string SettingsLocalFileName => "appsettings.Local.json"; protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var contentRoot = AppContext.BaseDirectory; var localSettingsPath = Path.Combine(contentRoot, SettingsLocalFileName); var builder = Microsoft.Extensions.Hosting.Host.CreateApplicationBuilder(); builder.Environment.ContentRootPath = contentRoot; builder.Configuration .SetBasePath(contentRoot) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true) .AddJsonFile(SettingsLocalFileName, optional: true, reloadOnChange: true) .AddEnvironmentVariables(prefix: "MARATHON_"); // Serilog — structured rolling-file + console. // Minimum level honours the "Serilog:MinimumLevel:Default" key when // present in configuration; otherwise defaults to Information. var logsDir = Path.Combine(contentRoot, "logs"); Directory.CreateDirectory(logsDir); var minimumLevel = ParseMinimumLevel(builder.Configuration["Serilog:MinimumLevel:Default"]); Log.Logger = new LoggerConfiguration() .MinimumLevel.Is(minimumLevel) .Enrich.FromLogContext() .WriteTo.Console() .WriteTo.File( path: Path.Combine(logsDir, "marathon-.log"), rollingInterval: RollingInterval.Day, retainedFileCountLimit: 14, shared: true) .CreateLogger(); builder.Services.AddSerilog(); // Marathon.UI services (Mud, localization, options, theme/locale state, settings writer). builder.Services.AddMarathonUi(builder.Configuration, localSettingsPath); // Blazor WebView root services. builder.Services.AddWpfBlazorWebView(); #if DEBUG builder.Services.AddBlazorWebViewDeveloperTools(); #endif // Application use cases + Infrastructure (persistence, scraping, workers). builder.Services.AddMarathonApplication(); builder.Services.AddMarathonInfrastructure(builder.Configuration); // MainWindow needs the IServiceProvider for BlazorWebView.Services binding. builder.Services.AddSingleton(); Host = builder.Build(); // Apply EF migrations + WAL pragma BEFORE Host.Start() so the BackgroundServices // (LiveOddsPoller, AnomalyDetectionPoller, etc.) don't race the DB schema creation. // Resolved in a scope because MarathonDbContextInitializer is Scoped (DbContext lifetime). using (var initScope = Host.Services.CreateScope()) { var initializer = initScope.ServiceProvider .GetRequiredService(); initializer.InitializeAsync().GetAwaiter().GetResult(); } Host.Start(); // Apply default culture from configuration before any UI renders. var localeOptions = Host.Services.GetRequiredService>().Value; var locale = Host.Services.GetRequiredService(); try { locale.Set(localeOptions.DefaultCulture); } catch (CultureNotFoundException) { locale.Set(LocaleState.Russian); } var window = Host.Services.GetRequiredService(); window.Show(); } private static Serilog.Events.LogEventLevel ParseMinimumLevel(string? raw) => Enum.TryParse(raw, ignoreCase: true, out var level) ? level : Serilog.Events.LogEventLevel.Information; protected override void OnExit(ExitEventArgs e) { try { Host?.StopAsync(TimeSpan.FromSeconds(5)).GetAwaiter().GetResult(); } catch (Exception ex) { Log.Error(ex, "Host shutdown failed"); } finally { Host?.Dispose(); Log.CloseAndFlush(); base.OnExit(e); } } }