import { createHash } from "node:crypto";
import { mkdir, readdir, readFile, rename, rm, writeFile } from "node:fs/promises";
import { dirname, join, posix } from "node:path";
import {
  odczytajPodpisanyDokument,
  PRZYPISANY_KLUCZ_PUBLICZNY_LICENCJI,
} from "./inccghfibgvxcyvvtkjp.mjs";

const MANIFEST_FILE_NAME = "manifest.json";
const UPDATE_STATE_FILE_NAME = "aktualizacja.json";
const VERSION_FILE_NAME = "pliki/wersja-aplikacji.json";
const PACKAGE_FILE_NAME = "pliki/package.json";
const MAX_MANIFEST_SIZE = 512 * 1024;
const MAX_FILE_SIZE = 25 * 1024 * 1024;
const MAX_UPDATE_SIZE = 150 * 1024 * 1024;
const MAX_UPDATE_FILES = 500;
const DOWNLOAD_TIMEOUT_MS = 30_000;
const VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
const SHA256_PATTERN = /^[a-f0-9]{64}$/i;
const MAX_ERROR_BODY = 16 * 1024;
const LOGIN_PATH = "api/logowanie";
const LICENCJA_PATH = "api/licencja";
const KLUCZ_RATUNKOWY_PATH = "api/klucz-ratunkowy";


export const LICENCJA_POWODY = new Set([
  "LOGOWANIE_WYMAGANE",
  "ZLE_DANE",
  "ABONAMENT_WYGASL",
  "AKTUALIZACJE_WSTRZYMANE",
  "ZA_DUZO_PROB",
]);

export function isLicencjaError(error) {
  return Boolean(error?.licencja) || LICENCJA_POWODY.has(String(error?.code || ""));
}

function bladLicencji(message, powod, opis = null) {
  const error = new Error(message || "Zaloguj się, żeby pobierać aktualizacje.");
  error.code = LICENCJA_POWODY.has(String(powod || "")) ? powod : "LOGOWANIE_WYMAGANE";
  error.licencja = true;
  
  
  if (opis) error.opis = opis;
  return error;
}
const ALLOWED_FILES = new Set([
  "start.htm",
  "pliki/mrmuaiqdpzjowpihiz.js",
  "pliki/kkpybpmhonxogbwetgp.js",
  "pliki/wwpfromllxxaibsmcggeilaesdhsxnfpjbkdhvfzn.js",
  "pliki/rsoboqwptvhomquiafjt.js",
  "pliki/vrjeksrdppugqlwzhfab.js",
  "pliki/xrhckdwhwcdxzqgbexgi.js",
  "pliki/pbvcrbtheayxdibsiziopspjvliemamswmymzletxoiriwob.js",
  "pliki/vhwsorwqtxdyzietldhegbsptp.css",
  "pliki/ycsqctiwsleqkdjoimum.js",
  "pliki/wypowiedzenieocdruk.pdf",
  "pliki/wypowiedzenieautomat.pdf",
  "pliki/wypowiedzenienabywcy.pdf",
  "pliki/serwer.mjs",
  "pliki/INSTRUKCJA-AUTOMATYZACJI.txt",
  "pliki/INSTRUKCJA-AKTUALIZACJI.txt",
  "pliki/Uruchom Baza Polis.cmd",
  "pliki/aktualizator.mjs",
]);




const ALLOWED_DIRECTORIES = [
  "pliki/assets/",
  "pliki/pwdjhucdpxshwlujddqk/",
  "pliki/fonts/",
  "pliki/narzedzia/",
  "pliki/vzhgqyjhppnpobjprzyl/",
  "pliki/llopfcklkccjjlikfqjj/",
  "pliki/tests/",
  "pliki/tools/",
  "pliki/vendor/",
  "trasy-towarzystw/",
];

export function normalizeRelativeUpdatePath(value) {
  const rawPath = String(value || "").trim().replaceAll("\\", "/");
  if (!rawPath || rawPath.includes("\0") || rawPath.startsWith("/") || /^[a-z]:/i.test(rawPath)) {
    throw new Error("Manifest zawiera nieprawidłową ścieżkę pliku.");
  }
  const normalizedPath = posix.normalize(rawPath);
  if (
    normalizedPath === "."
    || normalizedPath === ".."
    || normalizedPath.startsWith("../")
    || normalizedPath !== rawPath
  ) {
    throw new Error(`Manifest zawiera niedozwoloną ścieżkę: ${rawPath}`);
  }
  return normalizedPath;
}

export function isAllowedUpdatePath(filePath) {
  return ALLOWED_FILES.has(filePath)
    || ALLOWED_DIRECTORIES.some((directory) => filePath.startsWith(directory));
}

export function compareVersions(firstVersion, secondVersion) {
  const first = String(firstVersion || "").split(".").map(Number);
  const second = String(secondVersion || "").split(".").map(Number);
  for (let index = 0; index < 3; index += 1) {
    const firstPart = first[index] || 0;
    const secondPart = second[index] || 0;
    if (firstPart > secondPart) return 1;
    if (firstPart < secondPart) return -1;
  }
  return 0;
}

export function calculateSha256(contents) {
  return createHash("sha256").update(contents).digest("hex");
}

function validateVersion(value, label = "wersji") {
  const version = String(value || "").trim();
  if (!VERSION_PATTERN.test(version)) {
    throw new Error(`Nieprawidłowy numer ${label}. Oczekiwany format to np. 1.32.0.`);
  }
  return version;
}

function validateManifest(document) {
  if (!document || typeof document !== "object" || Array.isArray(document)) {
    throw new Error("Plik manifest.json nie zawiera prawidłowego obiektu JSON.");
  }
  const version = validateVersion(document.version, "wersji w manifest.json");
  if (!Array.isArray(document.files) || document.files.length === 0) {
    throw new Error("Manifest aktualizacji nie zawiera żadnych plików.");
  }
  if (document.files.length > MAX_UPDATE_FILES) {
    throw new Error(`Manifest może zawierać maksymalnie ${MAX_UPDATE_FILES} plików.`);
  }
  const paths = new Set();
  const files = document.files.map((entry) => {
    const path = normalizeRelativeUpdatePath(entry?.path);
    if (paths.has(path.toLocaleLowerCase("pl"))) {
      throw new Error(`Manifest zawiera plik więcej niż raz: ${path}`);
    }
    paths.add(path.toLocaleLowerCase("pl"));
    const sha256 = String(entry?.sha256 || "").trim().toLowerCase();
    if (!SHA256_PATTERN.test(sha256)) {
      throw new Error(`Brakuje prawidłowej sumy SHA-256 dla pliku: ${path}`);
    }
    const source = normalizeRelativeUpdatePath(entry?.source || path);
    return { path, source, sha256 };
  });
  return {
    version,
    publishedAt: typeof document.publishedAt === "string" ? document.publishedAt.slice(0, 80) : "",
    notes: typeof document.notes === "string" ? document.notes.trim().slice(0, 2_000) : "",
    files,
  };
}



function assertManifestFilesAllowed(manifest) {
  const blockedPaths = manifest.files.filter((file) => !isAllowedUpdatePath(file.path)).map((file) => file.path);
  if (blockedPaths.length === 0) return;
  console.error(`Aktualizacja ${manifest.version} zawiera pliki spoza listy dozwolonych: ${blockedPaths.join(", ")}`);
  throw new Error("Aktualizacja przygotowana na serwerze nie pasuje do tej wersji programu. Zgłoś problem autorowi aktualizacji.");
}



function czyDozwolonyAdresTestowy(url, allowInsecureLocalhost) {
  return allowInsecureLocalhost === true
    && url.protocol === "http:"
    && (url.hostname === "127.0.0.1" || url.hostname === "localhost");
}

function assertTrustedResponse(response, updateBaseUrl, allowInsecureLocalhost = false) {
  const responseUrl = new URL(response.url);
  const baseUrl = new URL(updateBaseUrl);
  const protokolOk = responseUrl.protocol === "https:"
    || czyDozwolonyAdresTestowy(responseUrl, allowInsecureLocalhost);
  if (!protokolOk || responseUrl.origin !== baseUrl.origin || !responseUrl.pathname.startsWith(baseUrl.pathname)) {
    throw new Error("Serwer aktualizacji przekierował pobieranie poza zaufany katalog.");
  }
}

async function odczytajJsonOdpowiedzi(response) {
  try {
    const contents = Buffer.from(await response.arrayBuffer());
    if (contents.length > MAX_ERROR_BODY) return null;
    const document = JSON.parse(contents.toString("utf8"));
    return document && typeof document === "object" && !Array.isArray(document) ? document : null;
  } catch {
    return null;
  }
}



function opisZDokumentu(document) {
  if (!document) return null;
  return {
    komunikat: typeof document.error === "string" ? document.error.slice(0, 300) : "",
    powod: typeof document.powod === "string" ? document.powod.slice(0, 40) : "",
    abonamentDo: typeof document.abonamentDo === "string" ? document.abonamentDo.slice(0, 10) : "",
    nazwa: typeof document.nazwa === "string" ? document.nazwa.slice(0, 60) : "",
    aktualizacjeWlaczone: document.aktualizacjeWlaczone,
  };
}

async function odczytajOpisOdmowy(response) {
  return opisZDokumentu(await odczytajJsonOdpowiedzi(response));
}




function assertBezPrzekierowania(response) {
  if (response.status >= 300 && response.status < 400) {
    throw new Error("Serwer licencji przekierował żądanie poza zaufany adres. Sprawdź adres kanału aktualizacji.");
  }
}

async function downloadBuffer(url, updateBaseUrl, maximumSize, { token = "", allowInsecureLocalhost = false } = {}) {
  let response;
  try {
    response = await fetch(url, {
      cache: "no-store",
      headers: {
        Accept: "application/json, application/octet-stream;q=0.9, */*;q=0.1",
        ...(token ? { Authorization: `Bearer ${token}` } : {}),
      },
      redirect: "follow",
      signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),
    });
  } catch (error) {
    if (error?.name === "TimeoutError") throw new Error("Serwer aktualizacji nie odpowiedział w wymaganym czasie.");
    throw new Error("Nie udało się połączyć z serwerem aktualizacji.");
  }
  assertTrustedResponse(response, updateBaseUrl, allowInsecureLocalhost);
  if (!response.ok) {
    if (response.status === 401 || response.status === 403 || response.status === 429) {
      const opis = await odczytajOpisOdmowy(response);
      throw bladLicencji(
        opis?.komunikat,
        opis?.powod || (response.status === 401 ? "LOGOWANIE_WYMAGANE" : ""),
        opis,
      );
    }
    if (response.status === 404) {
      const blad = new Error("Na serwerze nie ma jeszcze pliku manifest.json lub jednego z plików aktualizacji.");
      blad.code = "BRAK_PLIKU_WYDANIA";
      throw blad;
    }
    throw new Error(`Serwer aktualizacji zwrócił błąd HTTP ${response.status}.`);
  }
  const declaredSize = Number.parseInt(response.headers.get("content-length") || "0", 10);
  if (declaredSize > maximumSize) throw new Error("Pobierany plik aktualizacji jest zbyt duży.");
  const contents = Buffer.from(await response.arrayBuffer());
  if (contents.length > maximumSize) throw new Error("Pobierany plik aktualizacji jest zbyt duży.");
  return contents;
}

function buildRemoteFileUrl(updateBaseUrl, source) {
  const encodedSource = source.split("/").map((part) => encodeURIComponent(part)).join("/");
  return new URL(encodedSource, updateBaseUrl).href;
}



async function sprzatnijPorzuconePobrania(updateDirectory) {
  try {
    const wpisy = await readdir(updateDirectory, { withFileTypes: true });
    const granica = Date.now() - 24 * 60 * 60 * 1000;
    for (const wpis of wpisy) {
      if (!wpis.isDirectory() || !wpis.name.startsWith("pobieranie-")) continue;
      const czas = Number.parseInt(wpis.name.split("-")[1] || "", 10);
      if (Number.isFinite(czas) && czas > granica) continue;
      await rm(join(updateDirectory, wpis.name), { recursive: true, force: true }).catch(() => {});
    }
  } catch {
    
  }
}

async function readJsonFile(filePath) {
  try {
    return JSON.parse(await readFile(filePath, "utf8"));
  } catch {
    return null;
  }
}

export function createUpdateService({
  rootDirectory,
  updateBaseUrl,
  licencja = null,
  readAccountId = null,
  allowInsecureLocalhost = false,
  trustedPublicKey = PRZYPISANY_KLUCZ_PUBLICZNY_LICENCJI,
}) {
  const normalizedBaseUrl = new URL(updateBaseUrl);
  if (
    normalizedBaseUrl.protocol !== "https:"
    && !czyDozwolonyAdresTestowy(normalizedBaseUrl, allowInsecureLocalhost)
  ) {
    throw new Error("Adres aktualizacji musi używać HTTPS.");
  }
  if (!normalizedBaseUrl.pathname.endsWith("/")) normalizedBaseUrl.pathname += "/";
  const baseUrl = normalizedBaseUrl.href;
  
  const updateDirectory = join(rootDirectory, "pliki", "tmp", "aktualizacja");
  const pendingDirectory = join(updateDirectory, "oczekujaca");
  const pendingStatePath = join(pendingDirectory, UPDATE_STATE_FILE_NAME);
  const versionFilePath = join(rootDirectory, VERSION_FILE_NAME);
  const packageFilePath = join(rootDirectory, ...PACKAGE_FILE_NAME.split("/"));

  async function getCurrentVersion() {
    const versionDocument = await readJsonFile(versionFilePath);
    if (VERSION_PATTERN.test(String(versionDocument?.version || ""))) return versionDocument.version;
    const packageDocument = await readJsonFile(packageFilePath);
    return validateVersion(packageDocument?.version || "1.0.0", "zainstalowanej aplikacji");
  }

  async function getPendingUpdate() {
    const state = await readJsonFile(pendingStatePath);
    if (!state || state.formatVersion !== 1 || !VERSION_PATTERN.test(String(state.version || "")) || !Array.isArray(state.files)) {
      return null;
    }
    return state;
  }

  
  
  let tokenKanalu = null;

  async function zalogujDoKanalu({ login, haslo }) {
    const loginUrl = new URL(LOGIN_PATH, baseUrl).href;
    const wersja = await getCurrentVersion().catch(() => "");
    const idKonta = typeof readAccountId === "function" ? await readAccountId().catch(() => "") : "";
    let response;
    try {
      response = await fetch(loginUrl, {
        method: "POST",
        cache: "no-store",
        headers: { "Content-Type": "application/json", Accept: "application/json" },
        redirect: "manual",
        body: JSON.stringify({ login, haslo, wersja, idKonta: idKonta || "" }),
        signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),
      });
    } catch (error) {
      if (error?.name === "TimeoutError") throw new Error("Serwer aktualizacji nie odpowiedział w wymaganym czasie.");
      throw new Error("Nie udało się połączyć z serwerem aktualizacji.");
    }
    assertBezPrzekierowania(response);
    assertTrustedResponse(response, baseUrl, allowInsecureLocalhost);
    const dokument = await odczytajJsonOdpowiedzi(response);
    const opis = opisZDokumentu(dokument);
    if (!response.ok) {
      
      
      if (licencja?.zapamietajStanZSerwera && opis?.powod && opis.powod !== "ZLE_DANE") {
        await licencja.zapamietajStanZSerwera({
          nazwa: opis.nazwa,
          abonamentDo: opis.abonamentDo,
          aktualizacjeWlaczone: opis.aktualizacjeWlaczone,
          powod: opis.powod,
        }).catch(() => {});
      }
      if (response.status === 401 || response.status === 403 || response.status === 429) {
        throw bladLicencji(opis?.komunikat, opis?.powod, opis);
      }
      throw new Error(opis?.komunikat || `Serwer aktualizacji zwrócił błąd HTTP ${response.status}.`);
    }
    const token = String(dokument?.token || "");
    if (!token) throw new Error("Serwer aktualizacji nie potwierdził logowania.");
    if (licencja?.zapamietajStanZSerwera) {
      await licencja.zapamietajStanZSerwera({
        nazwa: dokument.nazwa,
        abonamentDo: dokument.abonamentDo,
        dniDoKonca: dokument.dniDoKonca,
        aktualizacjeWlaczone: dokument.aktualizacjeWlaczone,
        powod: "OK",
      }, { pelnyStan: true }).catch(() => {});
    }
    const wazneSekund = Number(dokument.wazneSekund);
    return {
      token,
      wygasa: Date.now() + (Number.isFinite(wazneSekund) && wazneSekund > 0 ? wazneSekund : 3_600) * 1000,
      nazwa: String(dokument.nazwa || ""),
      abonamentDo: String(dokument.abonamentDo || ""),
      dniDoKonca: Number.isFinite(Number(dokument.dniDoKonca)) ? Number(dokument.dniDoKonca) : null,
      aktualizacjeWlaczone: dokument.aktualizacjeWlaczone !== false,
    };
  }

  
  
  async function pobierzPodpisanaLicencje(daneLogowania = null) {
    const dane = daneLogowania || await licencja?.pobierzDaneLogowania?.().catch(() => null);
    if (!dane?.login || !dane?.haslo) {
      throw bladLicencji("Podaj login i hasło do programu.", "LOGOWANIE_WYMAGANE");
    }
    const adres = new URL(LICENCJA_PATH, baseUrl).href;
    const wersja = await getCurrentVersion().catch(() => "");
    const idKonta = typeof readAccountId === "function" ? await readAccountId().catch(() => "") : "";
    let response;
    try {
      response = await fetch(adres, {
        method: "POST",
        cache: "no-store",
        headers: { "Content-Type": "application/json", Accept: "application/json" },
        redirect: "manual",
        body: JSON.stringify({ login: dane.login, haslo: dane.haslo, wersja, idKonta: idKonta || "" }),
        signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),
      });
    } catch (error) {
      if (error?.name === "TimeoutError") throw new Error("Serwer licencji nie odpowiedział w wymaganym czasie.");
      throw new Error("Nie udało się połączyć z serwerem licencji.");
    }
    assertBezPrzekierowania(response);
    assertTrustedResponse(response, baseUrl, allowInsecureLocalhost);
    const dokument = await odczytajJsonOdpowiedzi(response);
    if (!response.ok) {
      const opis = opisZDokumentu(dokument);
      if (response.status === 401 || response.status === 403 || response.status === 429) {
        throw bladLicencji(opis?.komunikat, opis?.powod, opis);
      }
      throw new Error(opis?.komunikat || `Serwer licencji zwrócił błąd HTTP ${response.status}.`);
    }
    if (!dokument?.dokument || !dokument?.podpis || !dokument?.kluczPubliczny) {
      throw new Error("Serwer nie przysłał poprawnej licencji.");
    }
    return {
      dokument: String(dokument.dokument),
      podpis: String(dokument.podpis),
      kluczPubliczny: String(dokument.kluczPubliczny),
      nazwa: String(dokument.nazwa || ""),
      wazneDo: String(dokument.wazneDo || ""),
      programDziala: dokument.programDziala !== false,
      powodProgramu: String(dokument.powodProgramu || ""),
      aktualizacjeWlaczone: dokument.aktualizacjeWlaczone !== false,
    };
  }

  
  
  
  async function wyslijKluczRatunkowy(klucz) {
    const dane = await licencja?.pobierzDaneLogowania?.().catch(() => null);
    if (!dane?.login || !dane?.haslo) {
      throw bladLicencji("Podaj login i hasło do programu.", "LOGOWANIE_WYMAGANE");
    }
    const wartosc = String(klucz || "").trim().toLowerCase();
    if (!/^[0-9a-f]{64}$/.test(wartosc)) {
      throw new Error("Klucz ratunkowy ma nieprawidłową postać.");
    }
    const adres = new URL(KLUCZ_RATUNKOWY_PATH, baseUrl).href;
    const wersja = await getCurrentVersion().catch(() => "");
    const idKonta = typeof readAccountId === "function" ? await readAccountId().catch(() => "") : "";
    let response;
    try {
      response = await fetch(adres, {
        method: "POST",
        cache: "no-store",
        headers: { "Content-Type": "application/json", Accept: "application/json" },
        redirect: "manual",
        body: JSON.stringify({
          login: dane.login,
          haslo: dane.haslo,
          klucz: wartosc,
          wersja,
          idKonta: idKonta || "",
        }),
        signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),
      });
    } catch (error) {
      if (error?.name === "TimeoutError") throw new Error("Serwer licencji nie odpowiedział w wymaganym czasie.");
      throw new Error("Nie udało się połączyć z serwerem licencji.");
    }
    assertBezPrzekierowania(response);
    assertTrustedResponse(response, baseUrl, allowInsecureLocalhost);
    const dokument = await odczytajJsonOdpowiedzi(response);
    if (!response.ok) {
      const opis = opisZDokumentu(dokument);
      if (response.status === 401 || response.status === 403 || response.status === 429) {
        throw bladLicencji(opis?.komunikat, opis?.powod, opis);
      }
      throw new Error(opis?.komunikat || `Serwer licencji zwrócił błąd HTTP ${response.status}.`);
    }
    return true;
  }

  
  
  
  async function pobierzToken({ odswiez = false } = {}) {
    if (!licencja?.pobierzDaneLogowania) return "";
    if (!odswiez && tokenKanalu && tokenKanalu.wygasa > Date.now() + 60_000) return tokenKanalu.token;
    const dane = await licencja.pobierzDaneLogowania().catch(() => null);
    if (!dane?.login || !dane?.haslo) {
      tokenKanalu = null;
      return "";
    }
    const wynik = await zalogujDoKanalu(dane);
    tokenKanalu = { token: wynik.token, wygasa: wynik.wygasa };
    return tokenKanalu.token;
  }

  
  
  
  async function zapamietajOdmowe(error) {
    if (!licencja?.zapamietajStanZSerwera || !isLicencjaError(error) || error.code === "ZLE_DANE") return;
    await licencja.zapamietajStanZSerwera({
      nazwa: error.opis?.nazwa,
      abonamentDo: error.opis?.abonamentDo,
      aktualizacjeWlaczone: error.opis?.aktualizacjeWlaczone,
      powod: error.code,
    }).catch(() => {});
  }

  async function pobierzZAutoryzacja(url, maximumSize) {
    const token = await pobierzToken();
    try {
      return await downloadBuffer(url, baseUrl, maximumSize, { token, allowInsecureLocalhost });
    } catch (error) {
      
      
      if (token && error?.code === "LOGOWANIE_WYMAGANE") {
        tokenKanalu = null;
        const swiezyToken = await pobierzToken({ odswiez: true });
        if (swiezyToken) {
          return downloadBuffer(url, baseUrl, maximumSize, { token: swiezyToken, allowInsecureLocalhost });
        }
      }
      await zapamietajOdmowe(error);
      throw error;
    }
  }

  async function opisLicencji(dodatki = {}) {
    if (!licencja?.pobierzStan) return null;
    const stan = await licencja.pobierzStan().catch(() => null);
    if (!stan) return null;
    return { ...stan, ...dodatki };
  }

  
  
  async function sprawdzLicencje() {
    tokenKanalu = null;
    const dane = await licencja?.pobierzDaneLogowania?.().catch(() => null);
    if (!dane?.login || !dane?.haslo) {
      throw bladLicencji("Podaj login i hasło do aktualizacji.", "LOGOWANIE_WYMAGANE");
    }
    const wynik = await zalogujDoKanalu(dane);
    tokenKanalu = { token: wynik.token, wygasa: wynik.wygasa };
    return opisLicencji();
  }

  async function downloadManifest() {
    const manifestUrl = new URL(MANIFEST_FILE_NAME, baseUrl).href;
    const contents = await pobierzZAutoryzacja(manifestUrl, MAX_MANIFEST_SIZE);
    let document;
    try {
      document = JSON.parse(contents.toString("utf8"));
    } catch {
      throw new Error("Plik manifest.json na serwerze ma nieprawidłowy format JSON.");
    }
    const podpisanyManifest = odczytajPodpisanyDokument(document, trustedPublicKey);
    if (!podpisanyManifest) {
      throw new Error("Nie udało się potwierdzić podpisu manifestu aktualizacji.");
    }
    return validateManifest(podpisanyManifest);
  }

  async function checkForUpdate() {
    const currentVersion = await getCurrentVersion();
    const pending = await getPendingUpdate();
    if (pending && compareVersions(pending.version, currentVersion) > 0) {
      return {
        currentVersion,
        latestVersion: pending.version,
        updateAvailable: true,
        pending: true,
        notes: pending.notes || "",
        fileCount: pending.files.length,
        licencja: await opisLicencji(),
      };
    }
    let manifest;
    try {
      manifest = await downloadManifest();
    } catch (error) {
      
      
      
      
      if (error?.code === "BRAK_PLIKU_WYDANIA") {
        const rozpoznaniPrzezSerwer = Boolean(await pobierzToken().catch(() => ""));
        if (rozpoznaniPrzezSerwer) {
          return {
            currentVersion,
            latestVersion: currentVersion,
            updateAvailable: false,
            pending: false,
            notes: "",
            fileCount: 0,
            licencja: await opisLicencji(),
          };
        }
        throw new Error("Nie udało się sprawdzić aktualizacji na serwerze. Sprawdź połączenie z internetem.");
      }
      
      
      if (!isLicencjaError(error)) throw error;
      return {
        currentVersion,
        latestVersion: currentVersion,
        updateAvailable: false,
        pending: false,
        notes: "",
        fileCount: 0,
        licencja: await opisLicencji({ blad: true, powod: error.code, komunikat: error.message }),
      };
    }
    const updateAvailable = compareVersions(manifest.version, currentVersion) > 0;
    if (updateAvailable) assertManifestFilesAllowed(manifest);
    return {
      currentVersion,
      latestVersion: manifest.version,
      updateAvailable,
      pending: false,
      notes: manifest.notes,
      fileCount: manifest.files.length,
      licencja: await opisLicencji(),
    };
  }

  async function prepareUpdate() {
    const currentVersion = await getCurrentVersion();
    const existingPending = await getPendingUpdate();
    if (existingPending && compareVersions(existingPending.version, currentVersion) > 0) {
      return {
        currentVersion,
        latestVersion: existingPending.version,
        updateAvailable: true,
        pending: true,
        notes: existingPending.notes || "",
        fileCount: existingPending.files.length,
        licencja: await opisLicencji(),
      };
    }
    const manifest = await downloadManifest();
    if (compareVersions(manifest.version, currentVersion) <= 0) {
      return {
        currentVersion,
        latestVersion: manifest.version,
        updateAvailable: false,
        pending: false,
        notes: manifest.notes,
        fileCount: manifest.files.length,
        licencja: await opisLicencji(),
      };
    }
    assertManifestFilesAllowed(manifest);

    await mkdir(updateDirectory, { recursive: true });
    
    
    
    await sprzatnijPorzuconePobrania(updateDirectory);
    const downloadDirectory = join(updateDirectory, `pobieranie-${Date.now()}-${process.pid}`);
    const filesDirectory = join(downloadDirectory, "pliki");
    await mkdir(filesDirectory, { recursive: true });
    const preparedFiles = [];
    let totalSize = 0;
    try {
      for (const file of manifest.files) {
        const remoteUrl = buildRemoteFileUrl(baseUrl, file.source);
        const contents = await pobierzZAutoryzacja(remoteUrl, MAX_FILE_SIZE);
        totalSize += contents.length;
        if (totalSize > MAX_UPDATE_SIZE) throw new Error("Łączny rozmiar aktualizacji przekracza dozwolony limit.");
        if (calculateSha256(contents) !== file.sha256) {
          throw new Error(`Plik ${file.path} ma inną sumę kontrolną niż podana w manifest.json.`);
        }
        const stagedFilePath = join(filesDirectory, ...file.path.split("/"));
        await mkdir(dirname(stagedFilePath), { recursive: true });
        await writeFile(stagedFilePath, contents, { flag: "wx" });
        preparedFiles.push({ path: file.path, sha256: file.sha256, size: contents.length });
      }
      const state = {
        formatVersion: 1,
        version: manifest.version,
        createdAt: new Date().toISOString(),
        publishedAt: manifest.publishedAt,
        notes: manifest.notes,
        files: preparedFiles,
      };
      await writeFile(join(downloadDirectory, UPDATE_STATE_FILE_NAME), `${JSON.stringify(state, null, 2)}\n`, "utf8");
      await rm(pendingDirectory, { recursive: true, force: true });
      await rename(downloadDirectory, pendingDirectory);
      return {
        currentVersion,
        latestVersion: manifest.version,
        updateAvailable: true,
        pending: true,
        notes: manifest.notes,
        fileCount: preparedFiles.length,
        licencja: await opisLicencji(),
      };
    } catch (error) {
      await rm(downloadDirectory, { recursive: true, force: true }).catch(() => {});
      throw error;
    }
  }

  
  function zapomnijToken() {
    tokenKanalu = null;
  }

  return {
    checkForUpdate,
    getCurrentVersion,
    getPendingUpdate,
    prepareUpdate,
    sprawdzLicencje,
    pobierzPodpisanaLicencje,
    wyslijKluczRatunkowy,
    opisLicencji,
    zapomnijToken,
  };
}
