import { execFile } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs';
import * as path from 'path';
import { abgleichMisserfolg, alsIsoDatum, mitTemporaeremOrdner, type Abgleichauftrag, type Bezugsquelle, type QuellenAbgleich } from './basis';

const execFileAsync = promisify(execFile);

/** winget: lädt das Paket per `winget download` (nur unter Windows verfügbar). */
export class WingetQuelle implements Bezugsquelle {
  async gleicheAb(auftrag: Abgleichauftrag): Promise<QuellenAbgleich> {
    // In einen temporären Ordner laden — der bisherige Stand bleibt erhalten, falls etwas schiefgeht.
    const ablage = mitTemporaeremOrdner(auftrag.zielordner);
    try {
      const { stdout } = await execFileAsync(
        'winget',
        [
          'download',
          '--id',
          auftrag.wert,
          '--exact',
          '--download-directory',
          ablage.ordner,
          '--accept-package-agreements',
          '--accept-source-agreements',
          '--disable-interactivity',
        ],
        { timeout: 300000, windowsHide: true }
      );
      const geladeneDateien = fs.readdirSync(ablage.ordner);
      const installer = geladeneDateien.find((name) => /\.(exe|msi|msix|appx|appxbundle|zip)$/i.test(name));
      if (!installer) {
        ablage.verwerfen();
        return abgleichMisserfolg(auftrag.aktuelleVersion, 'winget hat keine Installer-Datei geliefert.');
      }
      // winget legt zusätzlich sein Manifest (.yaml) ab — im Ablageordner soll nur der Installer liegen.
      for (const name of geladeneDateien) {
        if (name !== installer) {
          fs.rmSync(path.join(ablage.ordner, name), { recursive: true, force: true });
        }
      }
      const version = versionAusAusgabe(stdout) || new Date().toISOString();
      ablage.uebernehmen();
      return {
        erfolg: true,
        geaendert: true,
        version,
        dateiname: installer,
        meldung: `Über winget geladen (${version}).`,
        // Das Veröffentlichungsdatum steht nur in der Paket-Auskunft, nicht in der Download-Ausgabe.
        veroeffentlichtAm: alsIsoDatum(await veroeffentlichungsdatum(auftrag.wert)),
      };
    } catch (fehler) {
      ablage.verwerfen();
      return abgleichMisserfolg(auftrag.aktuelleVersion, `winget fehlgeschlagen: ${fehler instanceof Error ? fehler.message : String(fehler)}`);
    }
  }
}

function versionAusAusgabe(ausgabe: string): string {
  const treffer = ausgabe.match(/[Vv]ersion[:\s]+([0-9][0-9.]*)/);
  return treffer ? treffer[1] : '';
}

/**
 * Fragt das Veröffentlichungsdatum beim Paket-Verzeichnis ab. `winget download`
 * gibt es nicht aus, `winget show` schon — dafür ein zweiter, kurzer Aufruf.
 */
async function veroeffentlichungsdatum(paketId: string): Promise<string> {
  try {
    const { stdout } = await execFileAsync(
      'winget',
      ['show', '--id', paketId, '--exact', '--accept-source-agreements', '--disable-interactivity'],
      { timeout: 120000, windowsHide: true }
    );
    const treffer = stdout.match(/(?:Release\s*Date|Ver[öo]ffentlich\w*)[:\s]+([0-9]{4}-[0-9]{2}-[0-9]{2})/i);
    return treffer ? treffer[1] : '';
  } catch {
    return ''; // ohne Datum ist die Zeile nur weniger aussagekräftig, kein Grund zu scheitern
  }
}
