import { randomUUID } from "node:crypto";
import { spawn } from "node:child_process";
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
import { join } from "node:path";

const DPAPI_ENTROPY = "Baza Polis Warta v1";
const PASSWORD_FILE_NAME = "warta-password.dpapi";
const GENERALI_PASSWORD_FILE_NAME = "generali-password.dpapi";
const MTU_PASSWORD_FILE_NAME = "mtu-password.dpapi";
const HESTIA_PASSWORD_FILE_NAME = "hestia-password.dpapi";
const TRASTI_PASSWORD_FILE_NAME = "trasti-password.dpapi";
const WIENER_PASSWORD_FILE_NAME = "wiener-password.dpapi";
const COMPENSA_PASSWORD_FILE_NAME = "compensa-password.dpapi";
const ALLIANZ_PASSWORD_FILE_NAME = "allianz-password.dpapi";
const INSLY_PASSWORD_FILE_NAME = "insly-password.dpapi";
const PZU_PASSWORD_FILE_NAME = "pzu-password.dpapi";


const LICENCJA_PASSWORD_FILE_NAME = "licencja-password.dpapi";
const LEGACY_RECORD_SELECTION_FILE_NAME = "warta-record.dpapi";
const POWERSHELL_PROTECT_SCRIPT = [
  "$ErrorActionPreference = 'Stop'",
  "Add-Type -AssemblyName System.Security",
  "$plain = [Console]::In.ReadToEnd()",
  "$bytes = [Text.Encoding]::UTF8.GetBytes($plain)",
  `$entropy = [Text.Encoding]::UTF8.GetBytes('${DPAPI_ENTROPY}')`,
  "$protected = [Security.Cryptography.ProtectedData]::Protect($bytes, $entropy, [Security.Cryptography.DataProtectionScope]::CurrentUser)",
  "[Console]::Out.Write([Convert]::ToBase64String($protected))",
].join("; ");
const POWERSHELL_UNPROTECT_SCRIPT = [
  "$ErrorActionPreference = 'Stop'",
  "Add-Type -AssemblyName System.Security",
  "$encoded = [Console]::In.ReadToEnd().Trim()",
  "$protected = [Convert]::FromBase64String($encoded)",
  `$entropy = [Text.Encoding]::UTF8.GetBytes('${DPAPI_ENTROPY}')`,
  "$bytes = [Security.Cryptography.ProtectedData]::Unprotect($protected, $entropy, [Security.Cryptography.DataProtectionScope]::CurrentUser)",
  "[Console]::Out.Write([Text.Encoding]::UTF8.GetString($bytes))",
].join("; ");

function runDpapiScript(script, input) {
  return new Promise((resolve, reject) => {
    const child = spawn("powershell.exe", [
      "-NoLogo",
      "-NoProfile",
      "-NonInteractive",
      "-ExecutionPolicy",
      "Bypass",
      "-Command",
      script,
    ], {
      windowsHide: true,
      stdio: ["pipe", "pipe", "pipe"],
    });
    const stdout = [];
    let stderrLength = 0;
    child.stdout.on("data", (chunk) => stdout.push(chunk));
    child.stderr.on("data", (chunk) => {
      stderrLength += chunk.length;
    });
    child.on("error", () => reject(new Error("Nie udało się uruchomić ochrony danych Windows.")));
    child.on("close", (exitCode) => {
      if (exitCode !== 0) {
        reject(new Error(stderrLength
          ? "Windows nie mógł zaszyfrować lub odszyfrować danych kalkulatora."
          : "Ochrona danych Windows zakończyła się błędem."));
        return;
      }
      resolve(Buffer.concat(stdout).toString("utf8"));
    });
    const inputBuffer = Buffer.from(String(input), "utf8");
    child.stdin.end(inputBuffer, () => inputBuffer.fill(0));
  });
}



export function dpapiProtectText(plainText) {
  return runDpapiScript(POWERSHELL_PROTECT_SCRIPT, plainText);
}

export function dpapiUnprotectText(protectedText) {
  return runDpapiScript(POWERSHELL_UNPROTECT_SCRIPT, protectedText);
}

export function createCredentialVault({ storageDirectory }) {
  const passwordFilePath = join(storageDirectory, PASSWORD_FILE_NAME);
  const generaliPasswordFilePath = join(storageDirectory, GENERALI_PASSWORD_FILE_NAME);
  const mtuPasswordFilePath = join(storageDirectory, MTU_PASSWORD_FILE_NAME);
  const hestiaPasswordFilePath = join(storageDirectory, HESTIA_PASSWORD_FILE_NAME);
  const trastiPasswordFilePath = join(storageDirectory, TRASTI_PASSWORD_FILE_NAME);
  const wienerPasswordFilePath = join(storageDirectory, WIENER_PASSWORD_FILE_NAME);
  const compensaPasswordFilePath = join(storageDirectory, COMPENSA_PASSWORD_FILE_NAME);
  const allianzPasswordFilePath = join(storageDirectory, ALLIANZ_PASSWORD_FILE_NAME);
  const inslyPasswordFilePath = join(storageDirectory, INSLY_PASSWORD_FILE_NAME);
  const pzuPasswordFilePath = join(storageDirectory, PZU_PASSWORD_FILE_NAME);
  const licencjaPasswordFilePath = join(storageDirectory, LICENCJA_PASSWORD_FILE_NAME);
  const legacyRecordSelectionFilePath = join(storageDirectory, LEGACY_RECORD_SELECTION_FILE_NAME);

  
  
  
  
  
  const decryptedSecretCache = new Map();
  
  
  
  const unreadableSecretCache = new Map();

  function fileFingerprint(fileStats) {
    return `${fileStats.size}:${fileStats.mtimeMs}`;
  }

  function createUnreadableCredentialError(cause) {
    const error = new Error(
      "Zapisane hasło jest niedostępne na tym koncie Windows. Wpisz i zapisz je ponownie w ustawieniach.",
      { cause },
    );
    error.code = "CREDENTIAL_UNREADABLE";
    return error;
  }

  async function hasProtectedFile(filePath) {
    try {
      const fileStats = await stat(filePath);
      if (!fileStats.isFile() || fileStats.size <= 0) return false;
      if (unreadableSecretCache.get(filePath) === fileFingerprint(fileStats)) return false;
      return Boolean(await loadProtectedValue(filePath));
    } catch {
      return false;
    }
  }

  async function saveProtectedValue(filePath, fileName, plainValue) {
    const protectedValue = await runDpapiScript(POWERSHELL_PROTECT_SCRIPT, plainValue);
    if (!protectedValue) throw new Error("Windows nie zwrócił zaszyfrowanych danych.");
    await mkdir(storageDirectory, { recursive: true });
    const temporaryPath = join(storageDirectory, `${fileName}.${randomUUID()}.tmp`);
    try {
      await writeFile(temporaryPath, protectedValue, { encoding: "utf8", mode: 0o600 });
      await rename(temporaryPath, filePath);
      unreadableSecretCache.delete(filePath);
      decryptedSecretCache.set(filePath, String(plainValue));
    } finally {
      await rm(temporaryPath, { force: true }).catch(() => {});
    }
  }

  async function loadProtectedValue(filePath) {
    if (decryptedSecretCache.has(filePath)) return decryptedSecretCache.get(filePath);
    let protectedValue;
    let fileStats;
    try {
      fileStats = await stat(filePath);
      protectedValue = await readFile(filePath, "utf8");
    } catch (error) {
      if (error?.code === "ENOENT") return "";
      throw error;
    }
    if (!protectedValue.trim()) return "";
    const fingerprint = fileFingerprint(fileStats);
    if (unreadableSecretCache.get(filePath) === fingerprint) {
      throw createUnreadableCredentialError();
    }
    unreadableSecretCache.delete(filePath);
    let plainValue;
    try {
      plainValue = await runDpapiScript(POWERSHELL_UNPROTECT_SCRIPT, protectedValue);
    } catch (error) {
      unreadableSecretCache.set(filePath, fingerprint);
      throw createUnreadableCredentialError(error);
    }
    
    
    if (plainValue) decryptedSecretCache.set(filePath, plainValue);
    return plainValue;
  }

  async function deleteProtectedValue(filePath) {
    decryptedSecretCache.delete(filePath);
    unreadableSecretCache.delete(filePath);
    await rm(filePath, { force: true });
  }

  async function hasWartaPassword() {
    return hasProtectedFile(passwordFilePath);
  }

  async function saveWartaPassword(password) {
    const plainPassword = String(password || "");
    if (!plainPassword) throw new Error("Hasło do Warty nie może być puste.");
    await saveProtectedValue(passwordFilePath, PASSWORD_FILE_NAME, plainPassword);
  }

  async function loadWartaPassword() {
    return loadProtectedValue(passwordFilePath);
  }

  async function deleteWartaPassword() {
    await deleteProtectedValue(passwordFilePath);
  }

  async function hasGeneraliPassword() {
    return hasProtectedFile(generaliPasswordFilePath);
  }

  async function saveGeneraliPassword(password) {
    const plainPassword = String(password || "");
    if (!plainPassword) throw new Error("Hasło do Generali nie może być puste.");
    await saveProtectedValue(generaliPasswordFilePath, GENERALI_PASSWORD_FILE_NAME, plainPassword);
  }

  async function loadGeneraliPassword() {
    return loadProtectedValue(generaliPasswordFilePath);
  }

  async function deleteGeneraliPassword() {
    await deleteProtectedValue(generaliPasswordFilePath);
  }

  async function hasMtuPassword() {
    return hasProtectedFile(mtuPasswordFilePath);
  }

  async function saveMtuPassword(password) {
    const plainPassword = String(password || "");
    if (!plainPassword) throw new Error("Hasło do MTU nie może być puste.");
    await saveProtectedValue(mtuPasswordFilePath, MTU_PASSWORD_FILE_NAME, plainPassword);
  }

  async function loadMtuPassword() {
    return loadProtectedValue(mtuPasswordFilePath);
  }

  async function deleteMtuPassword() {
    await deleteProtectedValue(mtuPasswordFilePath);
  }

  async function hasHestiaPassword() {
    return hasProtectedFile(hestiaPasswordFilePath);
  }

  async function saveHestiaPassword(password) {
    const plainPassword = String(password || "");
    if (!plainPassword) throw new Error("Hasło do Ergo Hestii nie może być puste.");
    await saveProtectedValue(hestiaPasswordFilePath, HESTIA_PASSWORD_FILE_NAME, plainPassword);
  }

  async function loadHestiaPassword() {
    return loadProtectedValue(hestiaPasswordFilePath);
  }

  async function deleteHestiaPassword() {
    await deleteProtectedValue(hestiaPasswordFilePath);
  }

  async function hasTrastiPassword() {
    return hasProtectedFile(trastiPasswordFilePath);
  }

  async function saveTrastiPassword(password) {
    const plainPassword = String(password || "");
    if (!plainPassword) throw new Error("Hasło do Trasti nie może być puste.");
    await saveProtectedValue(trastiPasswordFilePath, TRASTI_PASSWORD_FILE_NAME, plainPassword);
  }

  async function loadTrastiPassword() {
    return loadProtectedValue(trastiPasswordFilePath);
  }

  async function deleteTrastiPassword() {
    await deleteProtectedValue(trastiPasswordFilePath);
  }

  async function hasCompensaPassword() {
    return hasProtectedFile(compensaPasswordFilePath);
  }

  async function saveCompensaPassword(password) {
    const plainPassword = String(password || "");
    if (!plainPassword) throw new Error("Hasło do Compensy nie może być puste.");
    await saveProtectedValue(compensaPasswordFilePath, COMPENSA_PASSWORD_FILE_NAME, plainPassword);
  }

  async function loadCompensaPassword() {
    return loadProtectedValue(compensaPasswordFilePath);
  }

  async function deleteCompensaPassword() {
    await deleteProtectedValue(compensaPasswordFilePath);
  }

  async function hasWienerPassword() {
    return hasProtectedFile(wienerPasswordFilePath);
  }

  async function saveWienerPassword(password) {
    const plainPassword = String(password || "");
    if (!plainPassword) throw new Error("Hasło do Wiener nie może być puste.");
    await saveProtectedValue(wienerPasswordFilePath, WIENER_PASSWORD_FILE_NAME, plainPassword);
  }

  async function loadWienerPassword() {
    return loadProtectedValue(wienerPasswordFilePath);
  }

  async function deleteWienerPassword() {
    await deleteProtectedValue(wienerPasswordFilePath);
  }

  async function hasAllianzPassword() {
    return hasProtectedFile(allianzPasswordFilePath);
  }

  async function saveAllianzPassword(password) {
    const plainPassword = String(password || "");
    if (!plainPassword) throw new Error("Hasło do Allianz nie może być puste.");
    await saveProtectedValue(allianzPasswordFilePath, ALLIANZ_PASSWORD_FILE_NAME, plainPassword);
  }

  async function loadAllianzPassword() {
    return loadProtectedValue(allianzPasswordFilePath);
  }

  async function deleteAllianzPassword() {
    await deleteProtectedValue(allianzPasswordFilePath);
  }

  async function hasInslyPassword() {
    return hasProtectedFile(inslyPasswordFilePath);
  }

  async function saveInslyPassword(password) {
    const plainPassword = String(password || "");
    if (!plainPassword) throw new Error("Hasło do Superpolisy nie może być puste.");
    await saveProtectedValue(inslyPasswordFilePath, INSLY_PASSWORD_FILE_NAME, plainPassword);
  }

  async function loadInslyPassword() {
    return loadProtectedValue(inslyPasswordFilePath);
  }

  async function deleteInslyPassword() {
    await deleteProtectedValue(inslyPasswordFilePath);
  }

  async function hasPzuPassword() {
    return hasProtectedFile(pzuPasswordFilePath);
  }

  async function savePzuPassword(password) {
    const plainPassword = String(password || "");
    if (!plainPassword) throw new Error("Hasło do PZU nie może być puste.");
    await saveProtectedValue(pzuPasswordFilePath, PZU_PASSWORD_FILE_NAME, plainPassword);
  }

  async function loadPzuPassword() {
    return loadProtectedValue(pzuPasswordFilePath);
  }

  async function deletePzuPassword() {
    await deleteProtectedValue(pzuPasswordFilePath);
  }

  async function hasLicencjaPassword() {
    return hasProtectedFile(licencjaPasswordFilePath);
  }

  async function saveLicencjaPassword(password) {
    const plainPassword = String(password || "");
    if (!plainPassword) throw new Error("Hasło do aktualizacji nie może być puste.");
    await saveProtectedValue(licencjaPasswordFilePath, LICENCJA_PASSWORD_FILE_NAME, plainPassword);
  }

  async function loadLicencjaPassword() {
    return loadProtectedValue(licencjaPasswordFilePath);
  }

  async function deleteLicencjaPassword() {
    await deleteProtectedValue(licencjaPasswordFilePath);
  }

  async function deleteLegacyWartaRecordSelection() {
    await deleteProtectedValue(legacyRecordSelectionFilePath);
  }

  return {
    deleteAllianzPassword,
    deleteGeneraliPassword,
    deleteHestiaPassword,
    deleteInslyPassword,
    deleteLegacyWartaRecordSelection,
    deleteLicencjaPassword,
    deleteMtuPassword,
    deletePzuPassword,
    deleteTrastiPassword,
    deleteWienerPassword,
    deleteCompensaPassword,
    deleteWartaPassword,
    hasAllianzPassword,
    hasGeneraliPassword,
    hasHestiaPassword,
    hasInslyPassword,
    hasLicencjaPassword,
    hasMtuPassword,
    hasPzuPassword,
    hasTrastiPassword,
    hasWienerPassword,
    hasCompensaPassword,
    hasWartaPassword,
    loadAllianzPassword,
    loadGeneraliPassword,
    loadHestiaPassword,
    loadInslyPassword,
    loadLicencjaPassword,
    loadMtuPassword,
    loadPzuPassword,
    loadTrastiPassword,
    loadWienerPassword,
    loadCompensaPassword,
    loadWartaPassword,
    saveAllianzPassword,
    saveGeneraliPassword,
    saveHestiaPassword,
    saveInslyPassword,
    saveLicencjaPassword,
    saveMtuPassword,
    savePzuPassword,
    saveTrastiPassword,
    saveWienerPassword,
    saveCompensaPassword,
    saveWartaPassword,
  };
}
