using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Services;
using LehrerApp.Data;
using LehrerApp.Data.Repositories;
using LehrerApp.Desktop.Services;
using LehrerApp.Desktop.ViewModels;
using LehrerApp.Desktop.ViewModels.Groups;
using LehrerApp.Desktop.ViewModels.Planning;
using LehrerApp.Desktop.ViewModels.Settings;
using LehrerApp.Desktop.ViewModels.Students;
using LehrerApp.Desktop.ViewModels.Workload;
using LehrerApp.Sync;
using LehrerApp.Sync.Crypto;
using LehrerApp.Sync.Models;
using Microsoft.Extensions.DependencyInjection;
namespace LehrerApp.Desktop;
///
/// Konfiguriert den DI-Container für den Desktop-Client.
///
/// WICHTIG: Alle Repositories sind Singleton – eine LiteDB-Datei pro Nutzer.
/// Sync-Services werden nur registriert wenn eine Server-URL konfiguriert ist.
///
public static class AppBootstrapper
{
public static string DbPath { get; private set; } = "";
public static string AppDataPath { get; private set; } = "";
///
/// Vor gesetzt, wenn die Datenbank passwortgeschützt ist
/// (siehe App.axaml.cs: Passwort-Abfrage vor dem Öffnen der Datenbank).
///
public static string? DbPassword { get; set; }
///
/// Vor der eigentlichen DI-Konfiguration verfügbar (z.B. für den globalen
/// Exception-Handler in Program.cs, der schon vor greifen muss).
///
public static AppLogger Logger { get; private set; } = null!;
public static string ResolveAppDataPath()
{
if (!string.IsNullOrEmpty(AppDataPath)) return AppDataPath;
var appData = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"LehrerApp");
Directory.CreateDirectory(appData);
AppDataPath = appData;
return appData;
}
/// Vor dem Öffnen der Datenbank verfügbar (z.B. für die Passwort-Abfrage beim Start).
public static string ResolveDbPath()
{
if (string.IsNullOrEmpty(DbPath))
DbPath = Path.Combine(ResolveAppDataPath(), "lehrerapp.db");
return DbPath;
}
public static AppLogger EnsureLogger()
{
Logger ??= new AppLogger(ResolveAppDataPath());
return Logger;
}
public static bool IsDbEncrypted() =>
new DatabaseEncryptionService().IsEncrypted(ResolveDbPath());
/// Beendet den Prozess und startet ihn neu (z.B. nach Wiederherstellung eines Backups
/// oder einer Änderung der Datenbank-Verschlüsselung — die laufende LiteDB-Verbindung
/// kann nicht sicher "heiß" auf eine andere Datei umgehängt werden).
public static void RestartApplication()
{
var exePath = Environment.ProcessPath;
if (!string.IsNullOrEmpty(exePath))
System.Diagnostics.Process.Start(exePath);
Environment.Exit(0);
}
public static ServiceProvider BuildServices()
{
var services = new ServiceCollection();
// ── Pfade ─────────────────────────────────────────────────────────────
var appData = ResolveAppDataPath();
ResolveDbPath();
var queuePath = Path.Combine(appData, "syncqueue.db");
var keyPath = Path.Combine(appData, "sync.key");
// ── Logging & Benachrichtigungen ─────────────────────────────────────────
EnsureLogger();
services.AddSingleton(Logger);
services.AddSingleton();
// ── Datensicherheit (13.3) ───────────────────────────────────────────
// Backup läuft vor dem Öffnen der Datenbank, rein dateibasiert (unabhängig von
// Verschlüsselung) — reine Datei-Kopie, kein offener LiteDB-Handle nötig.
var backup = new BackupService(appData);
backup.CreateBackup(DbPath);
services.AddSingleton(backup);
services.AddSingleton(_ => new AppLockService(appData));
services.AddSingleton();
services.AddSingleton(_ => new PrivacySettingsService(appData));
services.AddSingleton();
services.AddSingleton();
// ── Datenbank ─────────────────────────────────────────────────────────
services.AddSingleton(_ => new LiteDbContext(DbPath, DbPassword));
// ── Repositories ──────────────────────────────────────────────────────
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
// ── Services ──────────────────────────────────────────────────────────
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton(_ => new SchoolCalendarSettingsService(appData));
services.AddSingleton(_ => new PeriodScheduleService(appData));
// ── Sync (optional – nur wenn Server konfiguriert) ────────────────────
services.AddSingleton(_ => new EventQueue(queuePath));
services.AddSingleton(sp => new ConflictResolver(sp.GetRequiredService()));
services.AddSingleton(_ =>
{
var key = SyncCrypto.LoadKey(keyPath) ?? SyncCrypto.GenerateKey();
SyncCrypto.SaveKey(key, keyPath);
return key;
});
var serverUrl = LoadServerUrl(appData);
var deviceId = LoadOrCreateDeviceId(appData);
if (!string.IsNullOrEmpty(serverUrl))
{
services.AddSingleton(sp => new SyncEngine(
sp.GetRequiredService(),
sp.GetRequiredService(),
BuildHttp(serverUrl, appData),
new SyncConfig
{
ServerUrl = serverUrl,
DeviceId = deviceId,
DeviceType = DeviceType.Desktop,
AutoSyncIntervalMinutes = 5,
}));
services.AddSingleton(sp => new SnapshotService(
BuildHttp(serverUrl, appData),
sp.GetRequiredService(),
sp.GetRequiredService(),
DeviceType.Desktop, DbPath, keyPath));
}
// ── ViewModels ────────────────────────────────────────────────────────
// Singleton: einmal erstellt, überall dieselbe Instanz
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton(sp =>
new SyncStatusViewModel(sp.GetService()));
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
// Transient: neue Instanz pro Navigation (für Detailseiten)
services.AddTransient();
services.AddTransient();
services.AddTransient();
services.AddTransient();
services.AddTransient();
services.AddTransient();
services.AddTransient();
return services.BuildServiceProvider();
}
// ── Hilfsmethoden ─────────────────────────────────────────────────────────
private static HttpClient BuildHttp(string url, string appData)
{
var http = new HttpClient { BaseAddress = new Uri(url) };
var tokenPath = Path.Combine(appData, "auth.token");
if (File.Exists(tokenPath))
http.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue(
"Bearer", File.ReadAllText(tokenPath).Trim());
return http;
}
public static string LoadServerUrl(string? path = null) =>
File.Exists(Path.Combine(path ?? AppDataPath, "server.txt"))
? File.ReadAllText(Path.Combine(path ?? AppDataPath, "server.txt")).Trim()
: "";
public static void SaveServerUrl(string url) =>
File.WriteAllText(Path.Combine(AppDataPath, "server.txt"), url);
private static string LoadOrCreateDeviceId(string appData)
{
var p = Path.Combine(appData, "device.id");
if (File.Exists(p)) return File.ReadAllText(p).Trim();
var id = Guid.NewGuid().ToString();
File.WriteAllText(p, id);
return id;
}
}