using LehrerApp.Core.Interfaces; using LehrerApp.Core.Services; using LehrerApp.Data; using LehrerApp.Data.Repositories; using LehrerApp.Desktop.ViewModels; using LehrerApp.Desktop.ViewModels.Groups; using LehrerApp.Desktop.ViewModels.Students; using LehrerApp.Sync; using LehrerApp.Sync.Crypto; using LehrerApp.Sync.Models; using Microsoft.Extensions.DependencyInjection; namespace LehrerApp.Desktop; public static class AppBootstrapper { public static IServiceProvider BuildServices() { var services = new ServiceCollection(); // ── Pfade ───────────────────────────────────────────────────────────── var appData = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "LehrerApp"); Directory.CreateDirectory(appData); var dbPath = Path.Combine(appData, "lehrerapp.db"); var queuePath = Path.Combine(appData, "syncqueue.db"); var keyPath = Path.Combine(appData, "sync.key"); // ── Datenbank ───────────────────────────────────────────────────────── services.AddSingleton(_ => new LiteDbContext(dbPath)); // ── Repositories ────────────────────────────────────────────────────── services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); // ── Core Services ───────────────────────────────────────────────────── services.AddSingleton(); services.AddSingleton(); // ── Sync ────────────────────────────────────────────────────────────── services.AddSingleton(_ => new EventQueue(queuePath)); services.AddSingleton(sp => new ConflictResolver(sp.GetRequiredService())); // Schlüssel laden oder neu generieren services.AddSingleton(_ => { var key = SyncCrypto.LoadKey(keyPath) ?? SyncCrypto.GenerateKey(); SyncCrypto.SaveKey(key, keyPath); return key; }); // EventApplier – wendet gezogene Events auf LiteDB an services.AddSingleton(sp => new EventApplier( key: sp.GetRequiredService(), students: sp.GetRequiredService(), groups: sp.GetRequiredService(), enrollments: sp.GetRequiredService(), exams: sp.GetRequiredService(), examResults: sp.GetRequiredService(), grades: sp.GetRequiredService(), units: sp.GetRequiredService(), lessons: sp.GetRequiredService(), docs: sp.GetRequiredService(), tasks: sp.GetRequiredService(), timeEntries: sp.GetRequiredService())); // SyncEngine + ReadableSnapshotService nur wenn Server konfiguriert var serverUrl = LoadServerUrl(appData); var deviceId = LoadOrCreateDeviceId(appData); services.AddSingleton(sp => { if (string.IsNullOrEmpty(serverUrl)) return null; var http = BuildHttpClient(serverUrl, appData); var config = new SyncConfig { ServerUrl = serverUrl, DeviceId = deviceId, DeviceType = DeviceType.Desktop, AutoSyncIntervalMinutes = 5, }; return new SyncEngine( sp.GetRequiredService(), sp.GetRequiredService(), http, config); }); services.AddSingleton(sp => { if (string.IsNullOrEmpty(serverUrl)) return null; return new ReadableSnapshotService( http: BuildHttpClient(serverUrl, appData), students: sp.GetRequiredService(), groups: sp.GetRequiredService(), enrollments: sp.GetRequiredService(), exams: sp.GetRequiredService(), examResults: sp.GetRequiredService(), grades: sp.GetRequiredService(), units: sp.GetRequiredService(), lessons: sp.GetRequiredService(), tasks: sp.GetRequiredService(), schoolYear: sp.GetRequiredService(), deviceId: deviceId); }); services.AddSingleton(sp => { if (string.IsNullOrEmpty(serverUrl)) return null; return new SnapshotService( http: BuildHttpClient(serverUrl, appData), db: sp.GetRequiredService(), syncKey: sp.GetRequiredService(), deviceType: DeviceType.Desktop, dbPath: dbPath, keyPath: keyPath); }); // ── ViewModels ──────────────────────────────────────────────────────── services.AddSingleton(); services.AddSingleton(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddSingleton(); services.AddTransient(sp => new DevicePairingViewModel( sp.GetRequiredService() ?? throw new InvalidOperationException( "Kein Server konfiguriert."))); return services.BuildServiceProvider(); } // ── Hilfsmethoden ───────────────────────────────────────────────────────── private static HttpClient BuildHttpClient(string serverUrl, string appData) { var http = new HttpClient { BaseAddress = new Uri(serverUrl) }; var tokenPath = Path.Combine(appData, "auth.token"); if (File.Exists(tokenPath)) { var token = File.ReadAllText(tokenPath).Trim(); http.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue( "Bearer", token); } return http; } private static string LoadServerUrl(string appData) { var path = Path.Combine(appData, "server.txt"); return File.Exists(path) ? File.ReadAllText(path).Trim() : ""; } private static string LoadOrCreateDeviceId(string appData) { var path = Path.Combine(appData, "device.id"); if (File.Exists(path)) return File.ReadAllText(path).Trim(); var id = Guid.NewGuid().ToString(); File.WriteAllText(path, id); return id; } }