











import { createCipheriv, createDecipheriv, createHash, randomBytes, randomUUID } from "node:crypto";
import { gunzipSync, gzipSync } from "node:zlib";
import { open, readFile, rename, rm } from "node:fs/promises";
import { userInfo } from "node:os";
import { dirname, join } from "node:path";
import { dpapiProtectText, dpapiUnprotectText } from "./pgaspyqkejxcmlrcfdbeltxttgjisgydu.mjs";

const MAGIC = Buffer.from("BPSZYFR1", "ascii");
const FLAG_GZIP = 0x01;
const HEADER_LENGTH = MAGIC.length + 1;
const IV_LENGTH = 12;
const TAG_LENGTH = 16;
const KEY_LENGTH = 32;
const MINIMUM_CONTAINER_LENGTH = HEADER_LENGTH + IV_LENGTH + TAG_LENGTH + 1;

export const KLUCZ_BAZY_FILE_NAME = "polisy-klucz.json";

export function czyZaszyfrowany(buffer) {
  return Buffer.isBuffer(buffer)
    && buffer.length >= MINIMUM_CONTAINER_LENGTH
    && buffer.subarray(0, MAGIC.length).equals(MAGIC);
}

export function zaszyfrujBufor(key, payload, { gzip = true } = {}) {
  if (!Buffer.isBuffer(key) || key.length !== KEY_LENGTH) {
    throw new Error("Klucz szyfrowania bazy ma nieprawidłową długość.");
  }
  const payloadBuffer = Buffer.isBuffer(payload) ? payload : Buffer.from(String(payload), "utf8");
  const flags = gzip ? FLAG_GZIP : 0;
  const header = Buffer.concat([MAGIC, Buffer.from([flags])]);
  const plain = gzip ? gzipSync(payloadBuffer) : payloadBuffer;
  const iv = randomBytes(IV_LENGTH);
  const cipher = createCipheriv("aes-256-gcm", key, iv);
  
  cipher.setAAD(header);
  const encrypted = Buffer.concat([cipher.update(plain), cipher.final(), cipher.getAuthTag()]);
  return Buffer.concat([header, iv, encrypted]);
}

export function odszyfrujBufor(key, container) {
  if (!Buffer.isBuffer(key) || key.length !== KEY_LENGTH) {
    throw new Error("Klucz szyfrowania bazy ma nieprawidłową długość.");
  }
  if (!czyZaszyfrowany(container)) {
    throw new Error("To nie jest zaszyfrowany plik bazy.");
  }
  const flags = container[MAGIC.length];
  const header = container.subarray(0, HEADER_LENGTH);
  const iv = container.subarray(HEADER_LENGTH, HEADER_LENGTH + IV_LENGTH);
  const body = container.subarray(HEADER_LENGTH + IV_LENGTH);
  const tag = body.subarray(body.length - TAG_LENGTH);
  const encrypted = body.subarray(0, body.length - TAG_LENGTH);
  const decipher = createDecipheriv("aes-256-gcm", key, iv);
  decipher.setAAD(header);
  decipher.setAuthTag(tag);
  const plain = Buffer.concat([decipher.update(encrypted), decipher.final()]);
  return (flags & FLAG_GZIP) ? gunzipSync(plain) : plain;
}

function nazwaKonta() {
  try {
    return String(userInfo().username || "");
  } catch {
    return "";
  }
}

export function normalizujKluczRatunkowy(value) {
  const hex = String(value || "").replace(/[^0-9a-f]/gi, "").toLowerCase();
  return hex.length === KEY_LENGTH * 2 ? hex : "";
}



async function atomicWrite(path, contents) {
  const temporaryPath = join(dirname(path), `.tmp-szyfr-${process.pid}-${randomUUID()}`);
  const handle = await open(temporaryPath, "wx", 0o600);
  try {
    await handle.writeFile(contents);
    await handle.sync();
  } catch (error) {
    await handle.close().catch(() => {});
    await rm(temporaryPath, { force: true }).catch(() => {});
    throw error;
  } finally {
    await handle.close().catch(() => {});
  }
  try {
    await rename(temporaryPath, path);
  } catch (error) {
    await rm(temporaryPath, { force: true }).catch(() => {});
    throw error;
  }
}







export function utworzSzyfrBazy({
  rootDirectory,
  protectText = dpapiProtectText,
  unprotectText = dpapiUnprotectText,
} = {}) {
  if (!rootDirectory) throw new TypeError("rootDirectory jest wymagany.");
  const storagePath = join(rootDirectory, KLUCZ_BAZY_FILE_NAME);
  let stan = "brak";
  let klucz = null;
  let ratunkowyPotwierdzonyFlaga = false;
  let utworzono = "";
  let kontoZPliku = "";
  
  
  let depozytOdcisk = "";

  async function zapiszPlikKlucza(keyBuffer, potwierdzony) {
    const protectedValue = await protectText(keyBuffer.toString("hex"));
    if (!protectedValue) throw new Error("Windows nie zwrócił zabezpieczonego klucza.");
    const createdAt = utworzono || new Date().toISOString();
    await atomicWrite(storagePath, `${JSON.stringify({
      wersja: 1,
      utworzono: createdAt,
      
      
      
      
      konto: nazwaKonta(),
      kluczDpapi: protectedValue,
      ratunkowyPotwierdzony: potwierdzony === true,
      depozytOdcisk: depozytOdcisk || "",
    }, null, 2)}\n`);
    stan = "aktywny";
    klucz = keyBuffer;
    ratunkowyPotwierdzonyFlaga = potwierdzony === true;
    utworzono = createdAt;
  }

  function odciskKlucza() {
    return stan === "aktywny" && klucz
      ? createHash("sha256").update(klucz).digest("hex").slice(0, 16)
      : "";
  }

  async function init() {
    let raw;
    try {
      raw = await readFile(storagePath, "utf8");
    } catch (error) {
      if (error?.code === "ENOENT") {
        stan = "brak";
        return stan;
      }
      throw error;
    }
    let parsed = null;
    try {
      parsed = JSON.parse(raw);
    } catch {
    }
    if (!parsed || parsed.wersja !== 1 || typeof parsed.kluczDpapi !== "string" || !parsed.kluczDpapi) {
      stan = "nieodczytany";
      return stan;
    }
    utworzono = typeof parsed.utworzono === "string" ? parsed.utworzono : "";
    ratunkowyPotwierdzonyFlaga = parsed.ratunkowyPotwierdzony === true;
    depozytOdcisk = typeof parsed.depozytOdcisk === "string" ? parsed.depozytOdcisk : "";
    kontoZPliku = typeof parsed.konto === "string" ? parsed.konto : "";
    try {
      const hex = normalizujKluczRatunkowy(await unprotectText(parsed.kluczDpapi));
      if (!hex) throw new Error("Odpakowany klucz ma nieprawidłową postać.");
      klucz = Buffer.from(hex, "hex");
      stan = "aktywny";
    } catch {
      
      stan = "nieodczytany";
      klucz = null;
    }
    return stan;
  }

  
  
  async function utworzKluczJesliBrak() {
    if (stan !== "brak") return stan;
    try {
      await zapiszPlikKlucza(randomBytes(KEY_LENGTH), false);
    } catch {
      stan = "niedostepny";
      klucz = null;
    }
    return stan;
  }

  
  
  
  function oznaczOczekiwanieNaRatunkowy() {
    if (stan === "brak" || stan === "aktywny") {
      stan = "nieodczytany";
      klucz = null;
    }
  }

  
  
  
  
  async function sprawdzZgodnoscZBaza(sciezkaBazy) {
    if (stan !== "aktywny" || !klucz) return stan;
    let bytes;
    try {
      bytes = await readFile(sciezkaBazy);
    } catch {
      return stan;
    }
    if (!czyZaszyfrowany(bytes)) return stan;
    try {
      odszyfrujBufor(klucz, bytes);
    } catch {
      oznaczOczekiwanieNaRatunkowy();
    }
    return stan;
  }

  async function przywrocZRatunkowego(value) {
    const hex = normalizujKluczRatunkowy(value);
    if (!hex) throw new Error("Klucz ratunkowy ma nieprawidłową postać.");
    
    await zapiszPlikKlucza(Buffer.from(hex, "hex"), true);
    return stan;
  }

  async function potwierdzRatunkowy() {
    if (stan !== "aktywny" || !klucz) throw new Error("Klucz szyfrowania nie jest aktywny.");
    if (ratunkowyPotwierdzonyFlaga) return;
    await zapiszPlikKlucza(klucz, true);
  }

  
  
  function depozytAktualny() {
    const odcisk = odciskKlucza();
    return Boolean(odcisk) && depozytOdcisk === odcisk;
  }

  async function zapiszDepozyt() {
    if (stan !== "aktywny" || !klucz) throw new Error("Klucz szyfrowania nie jest aktywny.");
    depozytOdcisk = odciskKlucza();
    await zapiszPlikKlucza(klucz, true);
  }

  function zaszyfruj(payload, options) {
    if (stan !== "aktywny" || !klucz) throw new Error("Klucz szyfrowania nie jest aktywny.");
    return zaszyfrujBufor(klucz, payload, options);
  }

  function odszyfruj(container) {
    if (stan !== "aktywny" || !klucz) throw new Error("Klucz szyfrowania nie jest aktywny.");
    return odszyfrujBufor(klucz, container);
  }

  
  async function odczytajPlik(filePath) {
    const bytes = await readFile(filePath);
    return czyZaszyfrowany(bytes) ? odszyfruj(bytes) : bytes;
  }

  return {
    init,
    utworzKluczJesliBrak,
    oznaczOczekiwanieNaRatunkowy,
    sprawdzZgodnoscZBaza,
    przywrocZRatunkowego,
    potwierdzRatunkowy,
    depozytAktualny,
    zapiszDepozyt,
    zaszyfruj,
    odszyfruj,
    odczytajPlik,
    stan: () => stan,
    aktywny: () => stan === "aktywny",
    klucz: () => klucz,
    kluczRatunkowyHex: () => (stan === "aktywny" && klucz ? klucz.toString("hex") : ""),
    ratunkowyPotwierdzony: () => ratunkowyPotwierdzonyFlaga,
    kontoKlucza: () => kontoZPliku,
    sciezkaPlikuKlucza: () => storagePath,
  };
}




async function zaszyfrujPlikWMiejscu(key, filePath) {
  let bytes;
  try {
    bytes = await readFile(filePath);
  } catch (error) {
    if (error?.code === "ENOENT") return "brak";
    throw error;
  }
  if (czyZaszyfrowany(bytes)) return "juz-zaszyfrowany";
  try {
    JSON.parse(bytes.toString("utf8").replace(/^﻿/, ""));
  } catch {
    return "nie-json";
  }
  const encrypted = zaszyfrujBufor(key, bytes);
  const roundtrip = odszyfrujBufor(key, encrypted);
  if (!roundtrip.equals(bytes)) {
    throw new Error("Weryfikacja szyfrowania nie powiodła się - plik pozostał bez zmian.");
  }
  await atomicWrite(filePath, encrypted);
  return "zaszyfrowany";
}



export async function migrujPlikiBazyDoSzyfru({ rootDirectory, key }) {
  if (!rootDirectory) throw new TypeError("rootDirectory jest wymagany.");
  if (!Buffer.isBuffer(key) || key.length !== KEY_LENGTH) {
    throw new Error("Klucz szyfrowania bazy ma nieprawidłową długość.");
  }
  const wynik = { zaszyfrowane: [], pominiete: [], bledy: [] };
  const filesToMigrate = [
    join(rootDirectory, "polisy-dane.json"),
    join(rootDirectory, "polisy-odpowiedzi.json"),
  ];
  for (const filePath of filesToMigrate) {
    try {
      const status = await zaszyfrujPlikWMiejscu(key, filePath);
      if (status === "zaszyfrowany") wynik.zaszyfrowane.push(filePath);
      else wynik.pominiete.push(`${filePath} (${status})`);
    } catch (error) {
      wynik.bledy.push(`${filePath}: ${error?.message || error}`);
    }
  }
  return wynik;
}
