import path from "node:path";
import os from "node:os";
import v8 from "node:v8";
import { app } from "electron";
import { getDebridLinkApiKeyIds } from "../shared/debrid-link-keys";
import {
  AddLinksPayload,
  AllDebridHostInfo,
  AppSettings,
  DebridAccountStatus,
  DebridProvider,
  DuplicatePolicy,
  EnableRemoteDiagnosticsInput,
  HistoryEntry,
  PackagePriority,
  ParsedPackageInput,
  RemoteDiagnosticsInfo,
  SessionStats,
  StartConflictEntry,
  StartConflictResolutionResult,
  UiSnapshot,
  UpdateCheckResult,
  UpdateInstallProgress,
  UpdateInstallResult
} from "../shared/types";
import { resetDebridLinkApiKeyDailyUsage, resetProviderDailyUsage } from "../shared/provider-daily-limits";
import { importDlcContainers } from "./container";
import { APP_VERSION, ONLINE_BACKUP_API_URL } from "./constants";
import { DownloadManager } from "./download-manager";
import { fetchAllDebridHostInfo, fetchDebridLinkHostLimits } from "./debrid";
import { checkAllDebridAccounts, checkMegaDebridAccount } from "./account-check";
import { parseMegaDebridAccounts } from "../shared/mega-debrid-accounts";
import { parseCollectorInput } from "./link-parser";
import { configureLogger, getLogFilePath, logger } from "./logger";
import { AllDebridWebFallback } from "./all-debrid-web";
import { BestDebridWebFallback } from "./bestdebrid-web";
import { RealDebridWebFallback } from "./realdebrid-web";
import { getItemLogPath, initItemLogs, shutdownItemLogs } from "./item-log";
import { getPackageLogPath, initPackageLogs, shutdownPackageLogs } from "./package-log";
import { initSessionLog, getSessionLogPath, shutdownSessionLog } from "./session-log";
import { MegaWebFallback } from "./mega-web-fallback";
import { addHistoryEntry, addHistoryEntryForRetention, cancelPendingAsyncSaves, clearHistory, createStoragePaths, loadHistory, loadHistoryForRetention, loadSessionWithStatus, loadSettings, normalizeHistoryEntry, normalizeLoadedSession, normalizeLoadedSessionTransientFields, normalizeSettings, removeHistoryEntry, resetHistoryForRetention, saveHistory, saveSession, saveSettings } from "./storage";
import { abortActiveUpdateDownload, checkGitHubUpdate, installLatestUpdate } from "./update";
import { runInstallWithResume } from "./update-install-flow";
import { rotateDebugToken, startDebugServer, stopDebugServer, restartDebugServer, getDebugServerRuntimeStatus, getActiveDebugToken, getDebugAllowlist, writeDebugServerConfig, clearDebugToken } from "./debug-server";
import { encodeConnectionCode, loadRemoteMeta, saveRemoteMeta } from "./connection-code";
import { encryptBackup, decryptBackup } from "./backup-crypto";
import { buildBackupPayload, planBackupImport, resolveRemoteDiagnosticsRestore, BackupRemoteDiagnostics } from "./backup-payload";
import { getAuditLogPath, initAuditLog, logAuditEvent, shutdownAuditLog } from "./audit-log";
import { initAccountRotationLog, shutdownAccountRotationLog } from "./account-rotation-log";
import { initConversionLog, shutdownConversionLog } from "./conversion-trace";
import { runStartupHealthCheck } from "./startup-health-check";
import { getDebugSetupCheck } from "./debug-setup";
import { buildLinkExportSelection, serializeLinkExportText } from "./link-export";
import { getRenameLogPath, initRenameLog, shutdownRenameLog } from "./rename-log";
import { getDesktopRenameLogPath, initDesktopRenameLog, shutdownDesktopRenameLog } from "./desktop-rename-log";
import { buildAccountSummary, diffAccountSummary } from "./support-data";
import { buildSupportBundle, getSupportBundleDefaultFileName } from "./support-bundle";
import { getTraceConfig, getTraceLogPath, initTraceLog, logTraceEvent, setTraceEnabled, shutdownTraceLog } from "./trace-log";
import type { DebugSetupCheckResult, SupportTraceConfig } from "../shared/types";
import { createOnlineBackup, downloadOnlineBackup, uploadOnlineBackup } from "./online-backup";

function sanitizeSettingsPatch(partial: Partial<AppSettings>): Partial<AppSettings> {
  const entries = Object.entries(partial || {}).filter(([, value]) => value !== undefined);
  return Object.fromEntries(entries) as Partial<AppSettings>;
}

function settingsFingerprint(settings: AppSettings): string {
  return JSON.stringify(normalizeSettings(settings));
}

export class AppController {
  private settings: AppSettings;

  private manager: DownloadManager;

  private megaWebFallback: MegaWebFallback;

  private realDebridWebFallback: RealDebridWebFallback;

  private allDebridWebFallback: AllDebridWebFallback;

  private bestDebridWebFallback: BestDebridWebFallback;

  private lastUpdateCheck: UpdateCheckResult | null = null;

  private lastUpdateCheckAt = 0;

  private storagePaths = createStoragePaths(path.join(app.getPath("userData"), "runtime"));

  private onStateHandler: ((snapshot: UiSnapshot) => void) | null = null;

  private autoResumePending = false;
  private runtimeStatsTimer: NodeJS.Timeout | null = null;
  private lastMemoryWarnAt = 0;

  public constructor() {
    configureLogger(this.storagePaths.baseDir);
    initSessionLog(this.storagePaths.baseDir);
    initPackageLogs(this.storagePaths.baseDir);
    initItemLogs(this.storagePaths.baseDir);
    initAuditLog(this.storagePaths.baseDir);
    initAccountRotationLog(this.storagePaths.baseDir);
    initConversionLog(this.storagePaths.baseDir);
    initRenameLog(this.storagePaths.baseDir);
    let desktopDir: string | null = null;
    try {
      desktopDir = app.getPath("desktop");
    } catch {
      desktopDir = null;
    }
    initDesktopRenameLog(desktopDir);
    initTraceLog(this.storagePaths.baseDir);
    this.settings = loadSettings(this.storagePaths);
    resetHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode);
    const loadResult = loadSessionWithStatus(this.storagePaths);
    const session = loadResult.session;
    this.megaWebFallback = new MegaWebFallback(() => ({
      login: this.settings.megaLogin,
      password: this.settings.megaPassword
    }));
    this.realDebridWebFallback = new RealDebridWebFallback(() => this.settings.rememberToken);
    this.allDebridWebFallback = new AllDebridWebFallback(() => this.settings.rememberToken);
    this.bestDebridWebFallback = new BestDebridWebFallback(() => this.settings.rememberToken);
    this.manager = new DownloadManager(this.settings, session, this.storagePaths, {
      megaWebUnrestrict: (link: string, signal?: AbortSignal, account?: { login: string; password: string }) => this.megaWebFallback.unrestrict(link, signal, account),
      allDebridWebUnrestrict: (link: string, signal?: AbortSignal) => this.allDebridWebFallback.unrestrict(link, signal),
      realDebridWebUnrestrict: (link: string, signal?: AbortSignal) => this.realDebridWebFallback.unrestrict(link, signal),
      bestDebridWebUnrestrict: (link: string, signal?: AbortSignal) => this.bestDebridWebFallback.unrestrict(link, signal),
      invalidateMegaSession: () => this.megaWebFallback.invalidateSession(),
      protectEmptyClobber: loadResult.status === "empty-unreadable",
      onHistoryEntry: (entry: HistoryEntry) => {
        addHistoryEntryForRetention(this.storagePaths, this.settings.historyRetentionMode, entry, this.historyLimits());
      }
    });
    this.manager.on("state", (snapshot: UiSnapshot) => {
      this.onStateHandler?.(snapshot);
    });
    logger.info(`App gestartet v${APP_VERSION}`);
    logger.info(`Log-Datei: ${getLogFilePath()}`);
    logAuditEvent("INFO", "App gestartet", {
      appVersion: APP_VERSION,
      runtimeDir: this.storagePaths.baseDir
    });
    try {
      const report = runStartupHealthCheck(this.settings, this.storagePaths);
      if (report.errorCount > 0 || report.warnCount > 0) {
        logger.warn(`Health-Check: ${report.errorCount} Fehler, ${report.warnCount} Warnungen, ${report.infoCount} Info`);
      } else {
        logger.info(`Health-Check: alles OK (${report.infoCount} Info)`);
      }
      for (const finding of report.findings) {
        const line = finding.hint
          ? `Health-Check [${finding.code}]: ${finding.message} — ${finding.hint}`
          : `Health-Check [${finding.code}]: ${finding.message}`;
        if (finding.severity === "ERROR") {
          logger.error(line);
        } else if (finding.severity === "WARN") {
          logger.warn(line);
        } else {
          logger.info(line);
        }
        if (finding.severity !== "INFO") {
          logAuditEvent(finding.severity, `Health-Check: ${finding.code}`, {
            message: finding.message,
            hint: finding.hint || ""
          });
        }
      }
    } catch (err) {
      logger.warn(`Health-Check uebersprungen (Fehler): ${String((err as Error).message || err)}`);
    }
    startDebugServer(this.manager, this.storagePaths.baseDir);
    this.runtimeStatsTimer = setInterval(() => {
      this.manager.persistRuntimeStats();
      this.settings = this.manager.getSettings();
      this.checkMemoryPressure();
    }, 60_000);
    this.runtimeStatsTimer.unref?.();

    if (this.settings.autoResumeOnStart) {
      const snapshot = this.manager.getSnapshot();
      const hasPending = Object.values(snapshot.session.items).some((item) => item.status === "queued" || item.status === "reconnect_wait");
      if (hasPending && this.hasAnyProviderToken(this.settings)) {
        if (this.onStateHandler) {
          this.beginAutoResume();
        } else {
          this.autoResumePending = true;
          logger.info("Auto-Resume beim Start vorgemerkt");
        }
      }
    }
  }

  // Early-warning for OOM on a long-running process. Measured against the V8
  // heap_size_limit (the real ceiling at which the process is killed), NOT against
  // heapTotal: V8 routinely runs near-full of its current heapTotal just before it
  // grows it, so a heapUsed/heapTotal ratio would cry wolf and — since every WARN
  // now feeds the error ring — crowd real failures out. Throttled to 1 warning per
  // 5 min so a genuine sustained-pressure run does not spam the log/ring.
  private checkMemoryPressure(): void {
    try {
      const mem = process.memoryUsage();
      const heapLimit = v8.getHeapStatistics().heap_size_limit;
      const ratio = heapLimit > 0 ? mem.heapUsed / heapLimit : 0;
      if (ratio < 0.9) {
        return;
      }
      const now = Date.now();
      if (now - this.lastMemoryWarnAt < 5 * 60_000) {
        return;
      }
      this.lastMemoryWarnAt = now;
      const mb = (bytes: number): number => Math.round(bytes / 1048576);
      logger.warn(
        `Speicherdruck: heapUsed=${mb(mem.heapUsed)}MB von Limit ${mb(heapLimit)}MB ` +
        `(${Math.round(ratio * 100)}%), heapTotal=${mb(mem.heapTotal)}MB, rss=${mb(mem.rss)}MB, external=${mb(mem.external)}MB`
      );
    } catch {
    }
  }

  private hasAnyProviderToken(settings: AppSettings): boolean {
    return Boolean(
      settings.token.trim()
      || settings.realDebridUseWebLogin
      || (settings.megaLogin.trim() && settings.megaPassword.trim())
      || settings.bestToken.trim()
      || settings.bestDebridUseWebLogin
      || settings.allDebridUseWebLogin
      || settings.allDebridToken.trim()
      || (settings.ddownloadLogin.trim() && settings.ddownloadPassword.trim())
      || settings.oneFichierApiKey.trim()
    );
  }

  public get onState(): ((snapshot: UiSnapshot) => void) | null {
    return this.onStateHandler;
  }

  public set onState(handler: ((snapshot: UiSnapshot) => void) | null) {
    this.onStateHandler = handler;
    if (handler) {
      handler(this.manager.getSnapshot());
      if (this.autoResumePending) {
        this.autoResumePending = false;
        this.beginAutoResume();
      } else {
        this.manager.triggerIdleExtractions();
      }
    }
  }

  private beginAutoResume(): void {
    void this.manager.getStartConflicts().then((conflicts) => {
      const excludePackageIds = new Set(conflicts.map((conflict) => conflict.packageId));
      if (excludePackageIds.size > 0) {
        const names = conflicts.map((conflict) => conflict.packageName).join(", ");
        logger.info(`Auto-Resume: ${excludePackageIds.size} Paket(e) mit Start-Konflikt zurückgehalten (${names}); übrige Pakete starten`);
      } else {
        logger.info("Auto-Resume beim Start aktiviert (keine Start-Konflikte)");
      }
      void this.manager.start(excludePackageIds.size > 0 ? { excludePackageIds } : undefined)
        .catch((err) => logger.warn(`Auto-Resume Start Fehler: ${String(err)}`));
    }).catch((err) => logger.warn(`Auto-Resume Konflikt-Check Fehler: ${String(err)}`));
  }

  public getSnapshot(): UiSnapshot {
    return this.manager.getSnapshot();
  }

  public getVersion(): string {
    return APP_VERSION;
  }

  public getSettings(): AppSettings {
    return this.settings;
  }

  public getAuditLogPath(): string | null {
    return getAuditLogPath();
  }

  public getRenameLogPath(): string | null {
    return getRenameLogPath();
  }

  public getDesktopRenameLogPath(): string | null {
    return getDesktopRenameLogPath();
  }

  public getTraceLogPath(): string | null {
    return getTraceLogPath();
  }

  public getTraceConfig(): SupportTraceConfig {
    return getTraceConfig();
  }

  public rotateDebugToken(): { path: string; token: string } {
    const rotated = rotateDebugToken(this.storagePaths.baseDir);
    this.audit("WARN", "Debug-Token rotiert", { path: rotated.path });
    return rotated;
  }

  private getSuggestedRemoteHosts(): string[] {
    const hosts: string[] = [];
    try {
      const interfaces = os.networkInterfaces();
      for (const entry of Object.values(interfaces)) {
        for (const net of entry || []) {
          if (net.family === "IPv4" && !net.internal && net.address) {
            hosts.push(net.address);
          }
        }
      }
    } catch {
    }
    return [...new Set(hosts)];
  }

  public getRemoteDiagnostics(): RemoteDiagnosticsInfo {
    const status = getDebugServerRuntimeStatus();
    const meta = loadRemoteMeta(this.storagePaths.baseDir);
    const token = getActiveDebugToken();
    const allowlist = getDebugAllowlist();
    const suggestedHosts = this.getSuggestedRemoteHosts();
    const host = meta.publicHost
      || (status.localOnly ? "127.0.0.1" : (suggestedHosts[0] || status.host));
    const code = (status.hasToken && token && host)
      ? encodeConnectionCode({ host, port: status.port, token, name: meta.name || undefined })
      : null;
    return {
      status,
      code,
      publicHost: meta.publicHost,
      name: meta.name,
      allowlist,
      suggestedHosts
    };
  }

  public async enableRemoteDiagnostics(input: EnableRemoteDiagnosticsInput): Promise<RemoteDiagnosticsInfo> {
    const baseDir = this.storagePaths.baseDir;
    const port = input.port && Number.isInteger(input.port) && input.port >= 1024 && input.port <= 65535
      ? input.port
      : 9868;
    const bindHost = input.hostMode === "network" ? "0.0.0.0" : "127.0.0.1";
    const allowlist = (input.allowlist || []).map((entry) => entry.trim()).filter((entry) => entry.length > 0);
    if (input.hostMode === "network" && allowlist.length === 0) {
      throw new Error("Netzwerk-Freigabe erfordert mindestens eine erlaubte IP oder CIDR in der Allowlist.");
    }
    let token = getActiveDebugToken();
    if (!token || input.rotateToken) {
      token = rotateDebugToken(baseDir).token;
    }
    writeDebugServerConfig({ host: bindHost, port, allowlist });
    saveRemoteMeta(baseDir, { publicHost: (input.publicHost || "").trim(), name: (input.name || "").trim() });
    await restartDebugServer();
    this.audit("WARN", "Ferndiagnose aktiviert", {
      host: bindHost,
      port,
      allowlistCount: allowlist.length,
      localOnly: input.hostMode === "local"
    });
    return this.getRemoteDiagnostics();
  }

  public async disableRemoteDiagnostics(): Promise<RemoteDiagnosticsInfo> {
    clearDebugToken();
    await restartDebugServer();
    this.audit("WARN", "Ferndiagnose deaktiviert (Token entfernt)");
    return this.getRemoteDiagnostics();
  }

  public async rotateRemoteDiagnosticsToken(): Promise<RemoteDiagnosticsInfo> {
    rotateDebugToken(this.storagePaths.baseDir);
    await restartDebugServer();
    this.audit("WARN", "Ferndiagnose-Token rotiert");
    return this.getRemoteDiagnostics();
  }

  private restoreRemoteDiagnosticsFromBackup(section: unknown, restartNow: boolean): void {
    const restore = resolveRemoteDiagnosticsRestore(section);
    if (!restore) {
      return;
    }
    writeDebugServerConfig({ host: restore.host, port: restore.port, allowlist: restore.allowlist });
    if (restartNow) {
      void restartDebugServer().catch(() => {});
    }
    this.audit("INFO", "Ferndiagnose-Einstellungen aus Backup wiederhergestellt", {
      port: restore.port ?? null,
      allowlistCount: restore.allowlist?.length ?? 0,
      host: restore.host ?? "unveraendert",
      restartNow
    });
  }

  public getDebugSetupCheck(): DebugSetupCheckResult {
    return getDebugSetupCheck(this.storagePaths.baseDir);
  }

  private audit(level: "INFO" | "WARN" | "ERROR", message: string, fields?: Record<string, unknown>): void {
    logAuditEvent(level, message, fields);
    logTraceEvent(level, "audit", message, fields);
  }

  public setTraceEnabled(enabled: boolean, note = "", durationMs?: number): SupportTraceConfig {
    const next = setTraceEnabled(enabled, note, durationMs);
    this.audit("INFO", enabled ? "Support-Trace aktiviert" : "Support-Trace deaktiviert", { note });
    return next;
  }

  // Carry the live, runtime-maintained usage/status counters onto a settings
  // object about to be applied, so they are never rolled back to a stale snapshot.
  // All-time totals take the max; daily/total usage and account statuses are taken
  // live; per-key Debrid-Link usage is filtered to keys that still exist.
  private overlayLiveUsageCounters(target: AppSettings): void {
    const liveSettings = this.manager.getSettings();
    target.totalDownloadedAllTime = Math.max(target.totalDownloadedAllTime || 0, liveSettings.totalDownloadedAllTime || 0);
    target.totalCompletedFilesAllTime = Math.max(target.totalCompletedFilesAllTime || 0, liveSettings.totalCompletedFilesAllTime || 0);
    target.totalRuntimeAllTimeMs = Math.max(target.totalRuntimeAllTimeMs || 0, this.manager.getLiveTotalRuntimeMs());
    target.providerDailyUsageDay = liveSettings.providerDailyUsageDay;
    target.providerDailyUsageBytes = { ...(liveSettings.providerDailyUsageBytes || {}) };
    target.providerTotalUsageBytes = { ...(liveSettings.providerTotalUsageBytes || {}) };
    target.debridLinkApiKeyDailyUsageBytes = Object.fromEntries(
      Object.entries(liveSettings.debridLinkApiKeyDailyUsageBytes || {}).filter(([keyId]) => getDebridLinkApiKeyIds(target.debridLinkApiKeys).includes(keyId))
    );
    target.debridLinkApiKeyTotalUsageBytes = Object.fromEntries(
      Object.entries(liveSettings.debridLinkApiKeyTotalUsageBytes || {}).filter(([keyId]) => getDebridLinkApiKeyIds(target.debridLinkApiKeys).includes(keyId))
    );
    target.debridAccountStatuses = { ...(liveSettings.debridAccountStatuses || {}) };
  }

  private applySettingsOnlyBackup(importedSettings: AppSettings, remoteDiagnostics?: unknown, restoreRemoteDiagnostics = false): void {
    const restoredSettings = normalizeSettings(importedSettings);
    this.overlayLiveUsageCounters(restoredSettings);
    this.settings = restoredSettings;
    saveSettings(this.storagePaths, this.settings);
    this.manager.setSettings(this.settings, { settingsOnlyImport: true });
    if (restoreRemoteDiagnostics) {
      this.restoreRemoteDiagnosticsFromBackup(remoteDiagnostics, true);
    }
  }

  public updateSettings(partial: Partial<AppSettings>): AppSettings {
    const sanitizedPatch = sanitizeSettingsPatch(partial);
    const previousSettings = this.settings;
    const nextSettings = normalizeSettings({
      ...previousSettings,
      ...sanitizedPatch
    });

    if (settingsFingerprint(nextSettings) === settingsFingerprint(previousSettings)) {
      return previousSettings;
    }

    this.overlayLiveUsageCounters(nextSettings);
    const retentionChanged = previousSettings.historyRetentionMode !== nextSettings.historyRetentionMode;
    const historyLimitsChanged = previousSettings.historyMaxEntries !== nextSettings.historyMaxEntries
      || previousSettings.historyMaxAgeDays !== nextSettings.historyMaxAgeDays;
    this.settings = nextSettings;
    if (retentionChanged) {
      resetHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode);
    } else if (historyLimitsChanged && this.settings.historyRetentionMode !== "never") {
      saveHistory(this.storagePaths, loadHistory(this.storagePaths), this.historyLimits());
    }
    saveSettings(this.storagePaths, this.settings);
    this.manager.setSettings(this.settings);
    this.audit("INFO", "Einstellungen aktualisiert", {
      changedKeys: Object.keys(sanitizedPatch),
      accountChanges: diffAccountSummary(previousSettings, this.settings)
    });
    if (previousSettings.rememberToken && !this.settings.rememberToken) {
      void this.realDebridWebFallback.clearSessions().catch((error) => {
        logger.warn(`Real-Debrid Web-Session konnte nicht gelöscht werden: ${String(error)}`);
      });
      void this.allDebridWebFallback.clearSessions().catch((error) => {
        logger.warn(`AllDebrid Web-Session konnte nicht gelöscht werden: ${String(error)}`);
      });
      void this.bestDebridWebFallback.clearSessions().catch((error) => {
        logger.warn(`BestDebrid Web-Session konnte nicht gelöscht werden: ${String(error)}`);
      });
    }
    return this.settings;
  }

  public resetProviderDailyUsage(provider: DebridProvider): AppSettings {
    const liveSettings = this.manager.getSettings();
    const nextSettings = normalizeSettings({
      ...liveSettings,
      ...resetProviderDailyUsage(liveSettings, provider)
    });
    this.settings = nextSettings;
    saveSettings(this.storagePaths, this.settings);
    this.manager.setSettings(this.settings);
    this.audit("INFO", "Provider-Tagesnutzung zurückgesetzt", { provider });
    return this.settings;
  }

  public resetDebridLinkApiKeyDailyUsage(keyId: string): AppSettings {
    const liveSettings = this.manager.getSettings();
    const nextSettings = normalizeSettings({
      ...liveSettings,
      ...resetDebridLinkApiKeyDailyUsage(liveSettings, keyId)
    });
    this.settings = nextSettings;
    saveSettings(this.storagePaths, this.settings);
    this.manager.setSettings(this.settings);
    this.audit("INFO", "Debrid-Link-Key-Tagesnutzung zurückgesetzt", { keyId });
    return this.settings;
  }

  public async openRealDebridLoginWindow(): Promise<void> {
    this.audit("INFO", "Real-Debrid Login-Fenster geöffnet");
    await this.realDebridWebFallback.openLoginWindow();
  }

  public async openAllDebridLoginWindow(): Promise<void> {
    this.audit("INFO", "AllDebrid Login-Fenster geöffnet");
    await this.allDebridWebFallback.openLoginWindow();
  }

  public async importBestDebridCookies(filePath: string): Promise<number> {
    const imported = await this.bestDebridWebFallback.importCookiesFromFile(filePath);
    this.audit("INFO", "BestDebrid Cookies importiert", {
      filePath,
      imported
    });
    return imported;
  }

  public async getAllDebridHostInfo(host = "rapidgator"): Promise<AllDebridHostInfo> {
    if (this.settings.allDebridUseWebLogin) {
      return this.allDebridWebFallback.getHostInfo(host);
    }
    const token = this.settings.allDebridToken.trim();
    if (!token) {
      throw new Error("AllDebrid ist nicht konfiguriert");
    }
    return fetchAllDebridHostInfo(token, host);
  }

  public async getDebridLinkHostLimits(host = "rapidgator") {
    return fetchDebridLinkHostLimits(this.settings.debridLinkApiKeys, host);
  }

public async checkDebridAccounts(): Promise<DebridAccountStatus[]> {
    const statuses = await checkAllDebridAccounts(this.settings);
    this.manager.applyDebridAccountStatuses(statuses);
    this.audit("INFO", "Debrid-Accounts geprueft", {
      total: statuses.length,
      valid: statuses.filter((s) => s.valid).length,
      premium: statuses.filter((s) => s.isPremium).length
    });
    return statuses;
  }

  public async checkSingleMegaDebridAccount(login: string, password: string): Promise<DebridAccountStatus | null> {
    const entry = parseMegaDebridAccounts(`${login.trim()}:${password.trim()}`)[0];
    if (!entry) {
      return null;
    }
    const status = await checkMegaDebridAccount(entry);
    this.manager.applyDebridAccountStatuses([status]);
    this.audit("INFO", "Mega-Debrid-Account einzeln geprueft", { valid: status.valid, premium: status.isPremium });
    return status;
  }
  public async checkUpdates(): Promise<UpdateCheckResult> {
    const result = await checkGitHubUpdate(this.settings.updateRepo);
    if (!result.error) {
      this.lastUpdateCheck = result;
      this.lastUpdateCheckAt = Date.now();
    }
    return result;
  }

  public async installUpdate(onProgress?: (progress: UpdateInstallProgress) => void): Promise<UpdateInstallResult> {
    const cacheAgeMs = Date.now() - this.lastUpdateCheckAt;
    const cached = this.lastUpdateCheck && !this.lastUpdateCheck.error && cacheAgeMs <= 10 * 60 * 1000
      ? this.lastUpdateCheck
      : undefined;
    const result = await runInstallWithResume(
      this.manager,
      () => installLatestUpdate(this.settings.updateRepo, cached, onProgress)
    );
    if (result.started) {
      this.lastUpdateCheck = null;
      this.lastUpdateCheckAt = 0;
    }
    return result;
  }

  public addLinks(payload: AddLinksPayload): { addedPackages: number; addedLinks: number; invalidCount: number } {
    const parsed = parseCollectorInput(payload.rawText, payload.packageName || this.settings.packageName);
    if (parsed.length === 0) {
      this.audit("WARN", "Links hinzufügen ohne gültigen Inhalt", {
        hasPackageName: Boolean(payload.packageName)
      });
      return { addedPackages: 0, addedLinks: 0, invalidCount: 1 };
    }
    const result = this.manager.addPackages(parsed);
    this.audit("INFO", "Links hinzugefügt", {
      addedPackages: result.addedPackages,
      addedLinks: result.addedLinks,
      requestedPackages: parsed.length
    });
    return { ...result, invalidCount: 0 };
  }

  public async addContainers(filePaths: string[]): Promise<{ addedPackages: number; addedLinks: number }> {
    const packages = await importDlcContainers(filePaths);
    const merged: ParsedPackageInput[] = packages.map((pkg) => ({
      name: pkg.name,
      links: pkg.links,
      ...(pkg.fileNames ? { fileNames: pkg.fileNames } : {})
    }));
    const result = this.manager.addPackages(merged);
    this.audit("INFO", "Container importiert", {
      files: filePaths.length,
      addedPackages: result.addedPackages,
      addedLinks: result.addedLinks
    });
    return result;
  }

  public async getStartConflicts(): Promise<StartConflictEntry[]> {
    return this.manager.getStartConflicts();
  }

  public async resolveStartConflict(packageId: string, policy: DuplicatePolicy): Promise<StartConflictResolutionResult> {
    return this.manager.resolveStartConflict(packageId, policy);
  }

  public clearAll(): void {
    this.audit("WARN", "Queue komplett geleert");
    this.manager.clearAll();
  }

  public async start(): Promise<void> {
    this.audit("INFO", "Session-Start ausgelöst");
    await this.manager.start();
  }

  public async startPackages(packageIds: string[]): Promise<void> {
    this.audit("INFO", "Paket-Start ausgelöst", { packageIds });
    await this.manager.startPackages(packageIds);
  }

  public async startItems(itemIds: string[]): Promise<void> {
    this.audit("INFO", "Item-Start ausgelöst", { itemIds });
    await this.manager.startItems(itemIds);
  }

  public stop(): void {
    this.audit("INFO", "Session-Stopp ausgelöst");
    this.manager.stop();
  }

  public togglePause(): boolean {
    const paused = this.manager.togglePause();
    this.audit("INFO", "Pause umgeschaltet", { paused });
    return paused;
  }

  public retryExtraction(packageId: string): void {
    this.audit("INFO", "Extraktion manuell wiederholt", { packageId });
    this.manager.retryExtraction(packageId);
  }

  public extractNow(packageId: string): void {
    this.audit("INFO", "Jetzt entpacken ausgelöst", { packageId });
    this.manager.extractNow(packageId);
  }

  public resetPackage(packageId: string): void {
    this.audit("INFO", "Paket zurückgesetzt", { packageId });
    this.manager.resetPackage(packageId);
  }

  public cancelPackage(packageId: string): void {
    this.audit("WARN", "Paket abgebrochen", { packageId });
    this.manager.cancelPackage(packageId);
  }

  public renamePackage(packageId: string, newName: string): void {
    this.audit("INFO", "Paket umbenannt", { packageId, newName });
    this.manager.renamePackage(packageId, newName);
  }

  public reorderPackages(packageIds: string[]): void {
    this.audit("INFO", "Paketreihenfolge geändert", { packageIds });
    this.manager.reorderPackages(packageIds);
  }

  public removeItem(itemId: string): void {
    this.audit("WARN", "Item entfernt", { itemId });
    this.manager.removeItem(itemId);
  }

  public togglePackage(packageId: string): void {
    this.audit("INFO", "Paket aktiviert/deaktiviert", { packageId });
    this.manager.togglePackage(packageId);
  }

  public exportPackageSelection(packageIds: string[]): { text: string; defaultFileName: string; packageCount: number; linkCount: number } {
    const selection = buildLinkExportSelection(this.manager.getSnapshot(), packageIds, []);
    this.audit("INFO", "Paket-Auswahl exportiert", {
      packageCount: selection.packageCount,
      linkCount: selection.linkCount,
      packageIds
    });
    return {
      text: serializeLinkExportText(selection.packages),
      defaultFileName: selection.defaultFileName,
      packageCount: selection.packageCount,
      linkCount: selection.linkCount
    };
  }

  public exportItemSelection(itemIds: string[]): { text: string; defaultFileName: string; packageCount: number; linkCount: number } {
    const selection = buildLinkExportSelection(this.manager.getSnapshot(), [], itemIds);
    this.audit("INFO", "Item-Auswahl exportiert", {
      packageCount: selection.packageCount,
      linkCount: selection.linkCount,
      itemIds
    });
    return {
      text: serializeLinkExportText(selection.packages),
      defaultFileName: selection.defaultFileName,
      packageCount: selection.packageCount,
      linkCount: selection.linkCount
    };
  }

  public exportQueue(): string {
    return this.manager.exportQueue();
  }

  public importQueue(json: string): { addedPackages: number; addedLinks: number } {
    const result = this.manager.importQueue(json);
    this.audit("INFO", "Import-Datei verarbeitet", result);
    return result;
  }

  public getSessionStats(): SessionStats {
    return this.manager.getSessionStats();
  }

  public resetSessionStats(): void {
    this.audit("INFO", "Session-Statistik zurückgesetzt");
    this.manager.resetSessionStats();
  }

  public resetDownloadStats(): void {
    this.manager.resetDownloadStats();
    this.settings = this.manager.getSettings();
    this.audit("INFO", "Download-Statistik zurückgesetzt");
  }

  public exportBackup(): Buffer {
    let remoteDiagnostics: BackupRemoteDiagnostics | undefined;
    if (Boolean(this.settings.backupIncludeRemoteDiagnostics)) {
      const status = getDebugServerRuntimeStatus();
      remoteDiagnostics = {
        allowlist: getDebugAllowlist(),
        port: status.port,
        hostMode: status.host === "0.0.0.0" ? "network" : "local"
      };
    }
    const payloadObj = buildBackupPayload({
      settings: { ...this.settings },
      appVersion: APP_VERSION,
      exportedAt: new Date().toISOString(),
      session: this.manager.getSession(),
      history: loadHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode, this.historyLimits()),
      remoteDiagnostics
    });
    this.audit("INFO", "Backup exportiert", {
      kind: payloadObj.kind,
      historyEntries: payloadObj.history ? payloadObj.history.length : 0,
      sessionItems: payloadObj.session ? Object.keys(payloadObj.session.items).length : 0,
      sessionPackages: payloadObj.session ? Object.keys(payloadObj.session.packages).length : 0
    });
    return encryptBackup(JSON.stringify(payloadObj));
  }

  public async exportOnlineBackup(): Promise<{ key: string }> {
    const created = createOnlineBackup({ ...this.settings }, APP_VERSION);
    await uploadOnlineBackup(created.record, ONLINE_BACKUP_API_URL);
    this.audit("INFO", "Online-Sicherung erstellt", { kind: "settings-only" });
    return { key: created.key };
  }

  public async importOnlineBackup(key: string): Promise<{ restored: boolean; relaunch: false; message: string }> {
    const payload = await downloadOnlineBackup(key, ONLINE_BACKUP_API_URL);
    this.applySettingsOnlyBackup(payload.settings);
    this.audit("INFO", "Online-Sicherung importiert", {
      kind: "settings-only",
      accountSummary: buildAccountSummary(this.settings)
    });
    return { restored: true, relaunch: false, message: "Einstellungen aus Online-Sicherung wiederhergestellt" };
  }

  public async exportSupportBundle(): Promise<{ buffer: Buffer; defaultFileName: string }> {
    this.audit("INFO", "Support-Bundle exportiert");
    logTraceEvent("INFO", "support", "Support-Bundle erstellt", {
      packageCount: Object.keys(this.manager.getSnapshot().session.packages).length,
      itemCount: Object.keys(this.manager.getSnapshot().session.items).length
    });
    return {
      buffer: await buildSupportBundle(this.manager, this.storagePaths.baseDir, { hostDiagnosticsMode: "cached" }),
      defaultFileName: getSupportBundleDefaultFileName()
    };
  }

  public getSupportBundleDefaultFileName(): string {
    return getSupportBundleDefaultFileName();
  }

  public importBackup(data: Buffer): { restored: boolean; relaunch: boolean; message: string } {
    let parsed: Record<string, unknown>;
    try {
      const json = decryptBackup(data);
      parsed = JSON.parse(json) as Record<string, unknown>;
    } catch {
      try {
        const json = data.toString("utf8");
        parsed = JSON.parse(json) as Record<string, unknown>;
      } catch {
        return { restored: false, relaunch: false, message: "Backup-Datei konnte nicht entschlüsselt werden" };
      }
    }
    const plan = planBackupImport(parsed);
    if (!plan.valid) {
      return { restored: false, relaunch: false, message: plan.message };
    }
    const hasSession = plan.restoreDownloads;

    const importedSettings = parsed.settings as AppSettings;
    const importedSettingsRecord = importedSettings as unknown as Record<string, unknown>;
    const currentSettingsRecord = this.settings as unknown as Record<string, unknown>;
    const SENSITIVE_KEYS: (keyof AppSettings)[] = [
      "token", "megaLogin", "megaPassword", "bestToken", "allDebridToken",
      "ddownloadLogin", "ddownloadPassword", "oneFichierApiKey",
      "debridLinkApiKeys", "linkSnappyLogin", "linkSnappyPassword",
      "notifyUrl"
    ];
    for (const key of SENSITIVE_KEYS) {
      const val = importedSettingsRecord[key];
      if (typeof val === "string" && val.startsWith("***")) {
        importedSettingsRecord[key] = currentSettingsRecord[key];
      }
    }
    const restoredSettings = normalizeSettings(importedSettings);

    // Settings-only backup: keep the running queue AND the live counters untouched.
    // Overlay the live usage/status counters so they don't roll back to the backup's
    // (older) snapshot (BUG I), and suppress the retroactive cleanup sweep so the
    // backup's cleanup policy can't purge the live completed queue here (BUG B) — the
    // policy still governs FUTURE completions through the normal path. Do NOT stop the
    // manager, wipe the session, block persistence or relaunch.
    if (!hasSession) {
      this.applySettingsOnlyBackup(restoredSettings, parsed.remoteDiagnostics, true);
      this.audit("INFO", "Backup importiert (nur Einstellungen)", {
        accountSummary: buildAccountSummary(this.settings)
      });
      return {
        restored: true,
        relaunch: false,
        message: "Einstellungen wiederhergestellt"
      };
    }

    this.settings = restoredSettings;
    saveSettings(this.storagePaths, this.settings);
    this.manager.setSettings(this.settings);

    this.manager.stop();
    this.manager.abortAllPostProcessing();
    this.manager.clearPersistTimer();
    cancelPendingAsyncSaves();

    const restoredSession = normalizeLoadedSessionTransientFields(
      normalizeLoadedSession(parsed.session)
    );
    saveSession(this.storagePaths, restoredSession);

    if (Array.isArray(parsed.history) && parsed.history.length > 0) {
      const normalizedHistory = (parsed.history as unknown[])
        .map((raw, idx) => normalizeHistoryEntry(raw, idx))
        .filter((entry): entry is HistoryEntry => entry !== null);
      if (normalizedHistory.length > 0) {
        saveHistory(this.storagePaths, normalizedHistory);
        logger.info(`Backup: ${normalizedHistory.length} History-Einträge wiederhergestellt`);
      }
    }

    resetHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode);

    this.restoreRemoteDiagnosticsFromBackup(parsed.remoteDiagnostics, false);

    this.manager.skipShutdownPersist = true;
    this.manager.blockAllPersistence = true;
    logger.info("Backup wiederhergestellt — App startet automatisch neu");
    this.audit("WARN", "Backup importiert", {
      historyEntries: Array.isArray(parsed.history) ? parsed.history.length : 0,
      accountSummary: buildAccountSummary(this.settings)
    });
    return { restored: true, relaunch: true, message: "Backup wiederhergestellt – App startet automatisch neu…" };
  }

  public getSessionLogPath(): string | null {
    return getSessionLogPath();
  }

  public getPackageLogPath(packageId: string): string | null {
    return this.manager.getPackageLogPath(packageId) || getPackageLogPath(packageId);
  }

  public getItemLogPath(itemId: string): string | null {
    return this.manager.getItemLogPath(itemId) || getItemLogPath(itemId);
  }

  public shutdown(): void {
    if (this.runtimeStatsTimer) {
      clearInterval(this.runtimeStatsTimer);
      this.runtimeStatsTimer = null;
    }
    stopDebugServer();
    abortActiveUpdateDownload();
    cancelPendingAsyncSaves();
    this.manager.prepareForShutdown();
    this.megaWebFallback.dispose();
    this.realDebridWebFallback.dispose();
    this.allDebridWebFallback.dispose();
    this.bestDebridWebFallback.dispose();
    shutdownSessionLog();
    shutdownPackageLogs();
    shutdownItemLogs();
    shutdownRenameLog();
    shutdownDesktopRenameLog();
    this.audit("INFO", "App beendet");
    shutdownTraceLog();
    shutdownAccountRotationLog();
    shutdownConversionLog();
    shutdownAuditLog();
    if (this.settings.historyRetentionMode === "session") {
      clearHistory(this.storagePaths);
    }
    logger.info("App beendet");
  }

  private historyLimits(): { maxEntries: number; maxAgeDays: number } {
    return { maxEntries: this.settings.historyMaxEntries, maxAgeDays: this.settings.historyMaxAgeDays };
  }

  public getHistory(): HistoryEntry[] {
    return loadHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode, this.historyLimits());
  }

  public clearHistory(): void {
    this.audit("WARN", "Verlauf geleert");
    clearHistory(this.storagePaths);
  }

  public setPackagePriority(packageId: string, priority: PackagePriority): void {
    this.audit("INFO", "Paket-Priorität geändert", { packageId, priority });
    this.manager.setPackagePriority(packageId, priority);
  }

  public skipItems(itemIds: string[]): void {
    this.audit("INFO", "Items übersprungen", { itemIds });
    this.manager.skipItems(itemIds);
  }

  public resetItems(itemIds: string[]): void {
    this.audit("INFO", "Items zurückgesetzt", { itemIds });
    this.manager.resetItems(itemIds);
  }

  public removeHistoryEntry(entryId: string): void {
    this.audit("INFO", "Verlaufseintrag entfernt", { entryId });
    removeHistoryEntry(this.storagePaths, entryId);
  }

  public addToHistory(entry: HistoryEntry): void {
    this.audit("INFO", "Verlaufseintrag hinzugefügt", {
      id: entry.id,
      name: entry.name,
      status: entry.status,
      provider: entry.provider,
      fileCount: entry.fileCount
    });
    addHistoryEntry(this.storagePaths, entry);
  }
}
