import fs from "node:fs";
import { spawnSync } from "node:child_process";

export interface WindowsHostEvent {
  timeCreated: string;
  id: number;
  providerName: string;
  levelDisplayName: string;
  message: string;
  bugcheckCode?: string;
  bugcheckCodeHex?: string;
  reportId?: string;
}

export interface WindowsHostDumpFile {
  name: string;
  fullName: string;
  length: number;
  lastWriteTime: string;
}

export interface WindowsCrashControlInfo {
  crashDumpEnabled: number | null;
  minidumpDir: string;
  dumpFile: string;
  overwrite: number | null;
  logEvent: number | null;
  autoReboot: number | null;
}

export interface WindowsHostDiagnostics {
  collectedAt: string;
  supported: boolean;
  platform: string;
  crashControl: WindowsCrashControlInfo | null;
  recentKernelPower: WindowsHostEvent[];
  recentWerKernel: WindowsHostEvent[];
  recentKernelDump: WindowsHostEvent[];
  recentAppCrashes: WindowsHostEvent[];
  recentMinidumps: WindowsHostDumpFile[];
  assessmentHints: string[];
  errors: string[];
}

const CACHE_TTL_MS = 15_000;

let cachedAt = 0;
let cachedValue: WindowsHostDiagnostics | null = null;

function createEmptyDiagnostics(): WindowsHostDiagnostics {
  return {
    collectedAt: new Date().toISOString(),
    supported: process.platform === "win32",
    platform: process.platform,
    crashControl: null,
    recentKernelPower: [],
    recentWerKernel: [],
    recentKernelDump: [],
    recentAppCrashes: [],
    recentMinidumps: [],
    assessmentHints: [],
    errors: []
  };
}

function runPowerShellJson(script: string): unknown {
  const result = spawnSync(
    process.env.ComSpec && process.env.ComSpec.toLowerCase().includes("pwsh") ? process.env.ComSpec : "powershell.exe",
    ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script],
    {
      encoding: "utf8",
      timeout: 20_000,
      windowsHide: true,
      stdio: ["ignore", "pipe", "pipe"]
    }
  );

  if (result.status !== 0) {
    const errorText = String(result.stderr || result.stdout || "").trim() || `PowerShell exited with code ${result.status}`;
    throw new Error(errorText);
  }

  const text = String(result.stdout || "").trim();
  if (!text) {
    return null;
  }
  return JSON.parse(text) as unknown;
}

function asRecord(value: unknown): Record<string, unknown> | null {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    return null;
  }
  return value as Record<string, unknown>;
}

function asString(value: unknown): string {
  return typeof value === "string" ? value : value === undefined || value === null ? "" : String(value);
}

function asNumber(value: unknown): number | null {
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : null;
}

function normalizeEvent(value: unknown): WindowsHostEvent | null {
  const record = asRecord(value);
  if (!record) {
    return null;
  }
  return {
    timeCreated: asString(record.TimeCreated),
    id: asNumber(record.Id) || 0,
    providerName: asString(record.ProviderName),
    levelDisplayName: asString(record.LevelDisplayName),
    message: asString(record.Message),
    bugcheckCode: asString(record.BugcheckCode),
    bugcheckCodeHex: asString(record.BugcheckCodeHex),
    reportId: asString(record.ReportId)
  };
}

function normalizeDumpFile(value: unknown): WindowsHostDumpFile | null {
  const record = asRecord(value);
  if (!record) {
    return null;
  }
  return {
    name: asString(record.Name),
    fullName: asString(record.FullName),
    length: asNumber(record.Length) || 0,
    lastWriteTime: asString(record.LastWriteTime)
  };
}

function normalizeCrashControl(value: unknown): WindowsCrashControlInfo | null {
  const record = asRecord(value);
  if (!record) {
    return null;
  }
  return {
    crashDumpEnabled: asNumber(record.CrashDumpEnabled),
    minidumpDir: asString(record.MinidumpDir),
    dumpFile: asString(record.DumpFile),
    overwrite: asNumber(record.Overwrite),
    logEvent: asNumber(record.LogEvent),
    autoReboot: asNumber(record.AutoReboot)
  };
}

function pushHints(diagnostics: WindowsHostDiagnostics): void {
  if (diagnostics.recentKernelPower.some((entry) => String(entry.bugcheckCode || "").trim() === "0")) {
    diagnostics.assessmentHints.push("Kernel-Power 41 mit BugcheckCode 0 deutet eher auf Freeze, Watchdog oder harten Reset als auf einen sauber erfassten klassischen BSOD hin.");
  }
  if (diagnostics.recentWerKernel.some((entry) => /watchdog/i.test(entry.message))) {
    diagnostics.assessmentHints.push("WER-Kernel meldet WATCHDOG-Live-Dumps. Das spricht eher fuer Kernel-, Treiber- oder Hardware-Stalls als fuer einen normalen User-Mode-App-Crash.");
  }
  if (diagnostics.recentAppCrashes.length === 0) {
    diagnostics.assessmentHints.push("Keine passenden Application-Error- oder Windows-Error-Reporting-Eintraege fuer den Downloader/Electron in den letzten Tagen gefunden.");
  }
  if (diagnostics.recentMinidumps.length === 0) {
    diagnostics.assessmentHints.push("Keine aktuellen Minidumps gefunden. Falls der Server erneut abstuerzt, sollte geprueft werden, ob Windows den Dump wirklich schreiben darf.");
  }
}

function loadFromPowerShell(): WindowsHostDiagnostics {
  const script = String.raw`
$ErrorActionPreference = "SilentlyContinue"

function Convert-EventRecord($eventRecord) {
  $map = @{}
  try {
    [xml]$xml = $eventRecord.ToXml()
    foreach ($node in $xml.Event.EventData.Data) {
      if ($node.Name) {
        $map[$node.Name] = [string]$node.'#text'
      }
    }
  } catch {
  }

  $reportId = ""
  if ([string]$eventRecord.Message -match "ReportId\s+([^,\r\n]+)") {
    $reportId = $Matches[1]
  }

  [PSCustomObject]@{
    TimeCreated = if ($eventRecord.TimeCreated) { $eventRecord.TimeCreated.ToUniversalTime().ToString("o") } else { "" }
    Id = [int]$eventRecord.Id
    ProviderName = [string]$eventRecord.ProviderName
    LevelDisplayName = [string]$eventRecord.LevelDisplayName
    Message = [string]$eventRecord.Message
    BugcheckCode = if ($map.ContainsKey("BugcheckCode")) { [string]$map["BugcheckCode"] } else { "" }
    BugcheckCodeHex = if ($map.ContainsKey("BugcheckCode") -and [int64]$map["BugcheckCode"] -gt 0) { ("0x{0:X}" -f [int64]$map["BugcheckCode"]) } else { "" }
    ReportId = $reportId
  }
}

$startTime = (Get-Date).AddDays(-7)
$crashControl = Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\CrashControl"

$kernelPower = @(
  Get-WinEvent -FilterHashtable @{ LogName = "System"; Id = 41; StartTime = $startTime } -MaxEvents 5 |
    ForEach-Object { Convert-EventRecord $_ }
)

$werKernel = @(
  Get-WinEvent -FilterHashtable @{ LogName = "Microsoft-Windows-WerKernel/Operational"; StartTime = $startTime } -MaxEvents 30 |
    Where-Object { $_.Message -match "WATCHDOG|dump|bugcheck|blue|memory" } |
    Select-Object -First 10 |
    ForEach-Object { Convert-EventRecord $_ }
)

$kernelDump = @(
  Get-WinEvent -FilterHashtable @{ LogName = "Microsoft-Windows-Kernel-Dump/Operational"; StartTime = $startTime } -MaxEvents 20 |
    Select-Object -First 10 |
    ForEach-Object { Convert-EventRecord $_ }
)

$appCrashes = @(
  Get-WinEvent -FilterHashtable @{ LogName = "Application"; StartTime = $startTime } -MaxEvents 100 |
    Where-Object {
      ($_.ProviderName -eq "Application Error" -or $_.ProviderName -eq "Windows Error Reporting") -and
      ($_.Message -match "Real-Debrid-Downloader|electron|node\.exe|main\.js")
    } |
    Select-Object -First 10 |
    ForEach-Object { Convert-EventRecord $_ }
)

$dumpFiles = @()
foreach ($dir in @("C:\Windows\Minidump", "C:\Windows\Minidumps")) {
  if (Test-Path $dir) {
    $dumpFiles += Get-ChildItem -Path $dir -File |
      Sort-Object LastWriteTime -Descending |
      Select-Object -First 10 |
      ForEach-Object {
        [PSCustomObject]@{
          Name = $_.Name
          FullName = $_.FullName
          Length = [int64]$_.Length
          LastWriteTime = $_.LastWriteTimeUtc.ToString("o")
        }
      }
  }
}

[PSCustomObject]@{
  CrashControl = [PSCustomObject]@{
    CrashDumpEnabled = if ($null -ne $crashControl.CrashDumpEnabled) { [int]$crashControl.CrashDumpEnabled } else { $null }
    MinidumpDir = [string]$crashControl.MinidumpDir
    DumpFile = [string]$crashControl.DumpFile
    Overwrite = if ($null -ne $crashControl.Overwrite) { [int]$crashControl.Overwrite } else { $null }
    LogEvent = if ($null -ne $crashControl.LogEvent) { [int]$crashControl.LogEvent } else { $null }
    AutoReboot = if ($null -ne $crashControl.AutoReboot) { [int]$crashControl.AutoReboot } else { $null }
  }
  RecentKernelPower = @($kernelPower)
  RecentWerKernel = @($werKernel)
  RecentKernelDump = @($kernelDump)
  RecentAppCrashes = @($appCrashes)
  RecentMinidumps = @($dumpFiles)
} | ConvertTo-Json -Depth 6 -Compress
`;

  const raw = runPowerShellJson(script);
  const parsed = asRecord(raw);
  const diagnostics = createEmptyDiagnostics();
  diagnostics.crashControl = normalizeCrashControl(parsed?.CrashControl ?? null);
  diagnostics.recentKernelPower = Array.isArray(parsed?.RecentKernelPower) ? parsed!.RecentKernelPower.map(normalizeEvent).filter(Boolean) as WindowsHostEvent[] : [];
  diagnostics.recentWerKernel = Array.isArray(parsed?.RecentWerKernel) ? parsed!.RecentWerKernel.map(normalizeEvent).filter(Boolean) as WindowsHostEvent[] : [];
  diagnostics.recentKernelDump = Array.isArray(parsed?.RecentKernelDump) ? parsed!.RecentKernelDump.map(normalizeEvent).filter(Boolean) as WindowsHostEvent[] : [];
  diagnostics.recentAppCrashes = Array.isArray(parsed?.RecentAppCrashes) ? parsed!.RecentAppCrashes.map(normalizeEvent).filter(Boolean) as WindowsHostEvent[] : [];
  diagnostics.recentMinidumps = Array.isArray(parsed?.RecentMinidumps) ? parsed!.RecentMinidumps.map(normalizeDumpFile).filter(Boolean) as WindowsHostDumpFile[] : [];
  diagnostics.collectedAt = new Date().toISOString();
  pushHints(diagnostics);
  return diagnostics;
}

export function getWindowsHostDiagnostics(forceRefresh = false): WindowsHostDiagnostics {
  if (!forceRefresh && cachedValue && Date.now() - cachedAt < CACHE_TTL_MS) {
    return cachedValue;
  }

  const diagnostics = createEmptyDiagnostics();
  if (process.platform !== "win32") {
    diagnostics.assessmentHints.push("Windows-Host-Diagnose ist nur unter Windows verfuegbar.");
    cachedAt = Date.now();
    cachedValue = diagnostics;
    return diagnostics;
  }

  try {
    const loaded = loadFromPowerShell();
    cachedAt = Date.now();
    cachedValue = loaded;
    return loaded;
  } catch (error) {
    diagnostics.errors.push(String(error instanceof Error ? error.message : error));
    diagnostics.assessmentHints.push("Host-Diagnose konnte nicht vollstaendig geladen werden.");
    cachedAt = Date.now();
    cachedValue = diagnostics;
    return diagnostics;
  }
}

export function getCachedWindowsHostDiagnostics(): WindowsHostDiagnostics | null {
  return cachedValue;
}

export function resetWindowsHostDiagnosticsCache(): void {
  cachedAt = 0;
  cachedValue = null;
}

export function hasRecentWindowsMinidumps(): boolean {
  for (const dir of ["C:\\Windows\\Minidump", "C:\\Windows\\Minidumps"]) {
    try {
      const entries = fs.readdirSync(dir, { withFileTypes: true });
      if (entries.some((entry) => entry.isFile())) {
        return true;
      }
    } catch {
    }
  }
  return false;
}
