import { readFileSync, writeFileSync, existsSync, renameSync, mkdirSync, copyFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { encrypt, decrypt } from './crypto.mjs';
import { CONFIG_PATH, LEGACY_CONFIG_PATH, DEFAULT_KONTO_ROOT } from './paths.mjs';

// Einmalige Migration: vor dem 2026-04-29-Refactor lag config.enc im Repo-Root statt im OS-AppData-Pfad.
// Wenn beim Start die neue Config fehlt, die alte aber existiert: verschieben + Hinweis loggen.
// Idempotent — laeuft nur einmal pro System.
function migrateLegacyConfig() {
  if (existsSync(CONFIG_PATH) || !existsSync(LEGACY_CONFIG_PATH)) return;
  mkdirSync(dirname(CONFIG_PATH), { recursive: true });
  renameSync(LEGACY_CONFIG_PATH, CONFIG_PATH);
  // Logger ist hier oft noch nicht initialisiert — bewusst console.error damit's auch im CLI sichtbar wird
  console.error(`[config] Migration: ${LEGACY_CONFIG_PATH} → ${CONFIG_PATH}`);
}

export const SUPPORTED_SOURCES = ['dkb', 'vw-bank', 'n26', 'fyrst', 'ing', 'gothaer', 'ovh', 'amazon', 'netcologne', 'internetx', 'captrader', 'vr-bank'];

export const SOURCE_DEFAULT_FOLDER = {
  'dkb': 'DKB',
  'vw-bank': 'VW-BANK',
  'n26': 'N26',
  'fyrst': 'fyrst',
  'ing': 'ING',
  'gothaer': 'Gothaer',
  'ovh': 'OVH',
  'amazon': 'Amazon',
  'netcologne': 'NetCologne',
  'internetx': 'InternetX',
  'captrader': 'CapTrader',
  'vr-bank': 'VR-Bank',
};

function emptyConfig() {
  return {
    version: 1,
    rootDir: DEFAULT_KONTO_ROOT,
    accounts: [],
  };
}

export function configExists() {
  migrateLegacyConfig();
  return existsSync(CONFIG_PATH);
}

export function loadConfig(password) {
  migrateLegacyConfig();
  if (!existsSync(CONFIG_PATH)) throw new Error(`Keine Config gefunden unter ${CONFIG_PATH} — zuerst "init" ausführen.`);
  const blob = readFileSync(CONFIG_PATH);
  let json;
  try {
    json = decrypt(blob, password);
  } catch (e) {
    // Crypto-Fehler beim Entschluesseln = falsches Master-Passwort. Saubere User-Meldung
    // statt Stack-Trace ueber Cipher-Internals (GCM-Auth-Tag-Mismatch o.ae.).
    const err = new Error('Falsches Master-Passwort.');
    err.code = 'WRONG_PASSWORD';
    err.cause = e;
    throw err;
  }
  let cfg;
  try {
    cfg = JSON.parse(json);
  } catch (e) {
    // Erfolgreich entschluesselt aber kein gueltiges JSON = Datei ist nicht eine
    // DocuDown-Config (vielleicht falsche Datei beim Import). PW war richtig (sonst
    // haette decrypt geworfen), nur Inhalt passt nicht.
    const err = new Error('Config-Datei ist beschaedigt oder keine gueltige DocuDown-Config.');
    err.cause = e;
    throw err;
  }
  if (!cfg.version) throw new Error('Config-Format ungültig.');
  if (cfg.kontoRoot && !cfg.rootDir) cfg.rootDir = cfg.kontoRoot;
  return cfg;
}

export function saveConfig(cfg, password) {
  mkdirSync(dirname(CONFIG_PATH), { recursive: true });
  const json = JSON.stringify(cfg, null, 2);
  const blob = encrypt(json, password);
  const tmp = `${CONFIG_PATH}.tmp`;
  writeFileSync(tmp, blob, { mode: 0o600 });
  renameSync(tmp, CONFIG_PATH);
}

// Export der verschluesselten config.enc an einen externen Pfad (USB-Backup, NAS).
// Master-Passwort bleibt zum Entschluesseln noetig.
export function exportConfigFile(targetPath) {
  if (!targetPath) throw new Error('Pfad fehlt.');
  if (!existsSync(CONFIG_PATH)) throw new Error(`Keine Config gefunden unter ${CONFIG_PATH}`);
  const dest = resolve(targetPath);
  mkdirSync(dirname(dest), { recursive: true });
  copyFileSync(CONFIG_PATH, dest);
  return dest;
}

// Import einer externen config.enc — ueberschreibt die aktuelle. Der Aufrufer
// muss sicherstellen, dass die Session danach gesperrt wird (neues Master-PW noetig).
//
// Schutz-Schichten:
//  1) Mindestgroesse-Check: eine config.enc hat IV+Salt+Tag-Header (≥50 Bytes).
//     Verhindert dass beliebige kleine Files als "Config" akzeptiert werden.
//  2) Backup der bestehenden Config: vor Ueberschreiben wird CONFIG_PATH.bak-<ts>
//     angelegt, damit ein versehentlicher Import nicht die alte Config zerstoert.
//     Aelteste Backups (>5) werden nicht automatisch entsorgt — bewusst, damit User
//     manuell entscheidet was gepruned wird.
//  Decrypt-Probe (Validation gegen falsches Master-PW) bleibt dem unlock() ueberlassen
//  — sonst muesste der Importer das PW kennen, was die UX verschlechtert.
export function importConfigFile(sourcePath) {
  if (!sourcePath) throw new Error('Pfad fehlt.');
  const src = resolve(sourcePath);
  if (!existsSync(src)) throw new Error(`Quelle nicht gefunden: ${src}`);

  const buf = readFileSync(src);
  if (buf.length < 50) {
    throw new Error('Quelle ist keine gueltige DocuDown-Config (Datei zu klein).');
  }

  mkdirSync(dirname(CONFIG_PATH), { recursive: true });

  // Backup vor Ueberschreiben — ein paar Dutzend KB Platz im AppData-Verzeichnis
  // sind ein faires Trade-off gegen Datenverlust durch Fehl-Import.
  if (existsSync(CONFIG_PATH)) {
    const backupPath = `${CONFIG_PATH}.bak-${Date.now()}`;
    try { copyFileSync(CONFIG_PATH, backupPath); } catch { /* nicht-fatal */ }
  }

  copyFileSync(src, CONFIG_PATH);
  return CONFIG_PATH;
}

export function newConfig(rootDir = DEFAULT_KONTO_ROOT) {
  const cfg = emptyConfig();
  cfg.rootDir = rootDir;
  return cfg;
}

export function findAccount(cfg, id) {
  return cfg.accounts.find((a) => a.id === id);
}

export function addAccount(cfg, account) {
  if (!SUPPORTED_SOURCES.includes(account.source)) {
    throw new Error(`Unbekannte Quelle: ${account.source}. Unterstützt: ${SUPPORTED_SOURCES.join(', ')}`);
  }
  if (!account.id) throw new Error('Account-ID fehlt.');
  if (findAccount(cfg, account.id)) throw new Error(`Account-ID "${account.id}" existiert bereits.`);
  cfg.accounts.push(account);
}

export function removeAccount(cfg, id) {
  const i = cfg.accounts.findIndex((a) => a.id === id);
  if (i < 0) throw new Error(`Account "${id}" nicht gefunden.`);
  cfg.accounts.splice(i, 1);
}

export function renameAccount(cfg, oldId, newId) {
  if (!newId) throw new Error('Neue Account-ID fehlt.');
  const acc = findAccount(cfg, oldId);
  if (!acc) throw new Error(`Account "${oldId}" nicht gefunden.`);
  if (oldId === newId) return;
  if (findAccount(cfg, newId)) throw new Error(`Account-ID "${newId}" existiert bereits.`);
  acc.id = newId;
}

// Layout-Settings (lokales Filesystem). Default: Year-Subfolder an.
export function getLayout(cfg) {
  return {
    yearSubfolder: cfg?.layout?.yearSubfolder ?? true,
  };
}

// Mail-Settings inkl. ZIP-Layout fuer den Steuerberater-Versand.
//   zipLayout 'flat-date-prefix' (Default): "<JJJJ-MM-TT> <Source> <Filename>" — chronologisch sortiert,
//                                            self-contained Filename, 1 Ordner.
//   zipLayout 'folder':                     "<Source>/<JJJJ-MM-TT> <Filename>" — strukturierte Ordner pro Quelle.
//   zipPassword (optional): AES-256-Encryption fuer DSGVO-konformen Mailversand. Empty/undefined = unverschluesselt.
export function getMailConfig(cfg) {
  const m = cfg?.mail || {};
  const smtp = cfg?.smtp || {};
  return {
    smtp,
    zipLayout: m.zipLayout || 'flat-date-prefix',
    zipPassword: m.zipPassword || '',
  };
}

export function setAccountOption(cfg, id, field, rawValue) {
  const acc = findAccount(cfg, id);
  if (!acc) throw new Error(`Account "${id}" nicht gefunden.`);
  if (!field) throw new Error('Feldname fehlt.');
  // Reservierte Felder nicht überschreiben
  if (['id', 'credentials'].includes(field)) throw new Error(`Feld "${field}" ist reserviert.`);
  // Leer/undefined → Feld entfernen
  if (rawValue === undefined || rawValue === '' || rawValue === 'null') {
    delete acc[field];
    return null;
  }
  // JSON-Parsing: "true", "false", Zahlen, Arrays, Objekte werden automatisch erkannt
  let value = rawValue;
  try { value = JSON.parse(rawValue); } catch { /* plain string */ }
  acc[field] = value;
  return value;
}
