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);
}
}
}