"use strict";

const path = require("path");
const fs = require("fs-extra");
const AdmZip = require("adm-zip");
const crypto = require("crypto");

const ROOT_DIR = path.resolve(__dirname, "..");
const RELEASE_DIR = path.join(ROOT_DIR, ".release");
const SOURCE_DIR = path.join(RELEASE_DIR, "source");
const ARTIFACTS_DIR = path.join(RELEASE_DIR, "artifacts");
const CAPROVER_CONTEXT_DIR = path.join(RELEASE_DIR, "caprover-context");
const SERVER_DIR = path.join(ROOT_DIR, "deploy", "server");
const RELEASES_DIR = path.join(SERVER_DIR, "releases");
const APP_NAME = "pi-network";
const DEFAULT_BASE_URL = "https://pinetwork.deploy.ecoquimic.com";
const EXCLUDE_ROOTS = new Set(["node_modules", ".git", ".release", ".updates"]);
const EXCLUDE_FILES = new Set(["Thumbs.db", ".DS_Store", "npm-debug.log"]);

function sanitizeBaseUrl(url) {
  return url.endsWith("/") ? url.slice(0, -1) : url;
}

function shouldIncludePath(relPath) {
  if (!relPath || relPath === ".") {
    return true;
  }

  const segments = relPath.split(path.sep).filter(Boolean);
  if (!segments.length) {
    return true;
  }

  if (EXCLUDE_ROOTS.has(segments[0])) {
    return false;
  }

  const base = segments[segments.length - 1];
  if (EXCLUDE_FILES.has(base)) {
    return false;
  }

  return true;
}

async function copyProjectToSource() {
  const topLevelEntries = await fs.readdir(ROOT_DIR);

  for (const entry of topLevelEntries) {
    if (!shouldIncludePath(entry)) {
      continue;
    }

    const sourcePath = path.join(ROOT_DIR, entry);
    const targetPath = path.join(SOURCE_DIR, entry);

    await fs.copy(sourcePath, targetPath, {
      overwrite: true,
      errorOnExist: false,
      filter: (itemPath) =>
        shouldIncludePath(path.relative(ROOT_DIR, itemPath)),
    });
  }
}

async function prepareDeployPlaceholder() {
  const serverDirInSource = path.join(SOURCE_DIR, "deploy", "server");
  if (await fs.pathExists(serverDirInSource)) {
    await fs.emptyDir(serverDirInSource);
    await fs.ensureFile(path.join(serverDirInSource, ".gitkeep"));
  }
}

async function listTopLevelEntries() {
  const entries = await fs.readdir(SOURCE_DIR);
  const files = [];

  for (const name of entries) {
    if (name === "update-manifest.json") {
      continue;
    }

    const candidate = path.join(SOURCE_DIR, name);
    const stats = await fs.stat(candidate);
    files.push({
      path: name,
      type: stats.isDirectory() ? "directory" : "file",
    });
  }

  const requiresInstall = files.some((item) => {
    return item.path === "package.json" || item.path === "package-lock.json";
  });

  return { files, requiresInstall };
}

function writeZipFromSource(targetPath) {
  const zip = new AdmZip();
  zip.addLocalFolder(SOURCE_DIR, "");
  zip.writeZip(targetPath);
}

async function fileSha256(filePath) {
  const hash = crypto.createHash("sha256");
  const stream = fs.createReadStream(filePath);

  await new Promise((resolve, reject) => {
    stream.on("error", reject);
    stream.on("data", (chunk) => hash.update(chunk));
    stream.on("end", resolve);
  });

  return hash.digest("hex");
}

function createIndexHtml(versionManifest) {
  return `<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>${APP_NAME} updates</title>
  <style>
    :root { font-family: system-ui, -apple-system, Segoe UI, sans-serif; background: #0b1120; color: #e2e8f0; }
    body { margin: 0; padding: 3rem; display: flex; align-items: center; justify-content: center; min-height: 100vh; }
    main { background: rgba(15, 23, 42, 0.8); border-radius: 1rem; padding: 2rem 3rem; max-width: 540px; box-shadow: 0 25px 50px -12px rgba(15, 23, 42, 0.75); }
    h1 { margin-top: 0; font-size: 1.9rem; }
    pre { background: rgba(15, 23, 42, 0.6); padding: 1rem; border-radius: 0.75rem; overflow-x: auto; }
    a { color: #38bdf8; text-decoration: none; }
    a:hover { text-decoration: underline; }
  </style>
</head>
<body>
  <main>
    <h1>${APP_NAME} – última versión</h1>
    <p>Descarga directa: <a href="${versionManifest.downloadUrl}">${versionManifest.downloadUrl}</a></p>
    <pre>${JSON.stringify(versionManifest, null, 2)}</pre>
  </main>
</body>
</html>`;
}

async function buildRelease() {
  const baseUrl = sanitizeBaseUrl(
    process.env.PI_NETWORK_UPDATE_BASE_URL || DEFAULT_BASE_URL
  );
  const pkgPath = path.join(ROOT_DIR, "package.json");
  const pkg = await fs.readJson(pkgPath);
  const version = pkg.version;

  if (!version) {
    throw new Error("package.json does not contain a version field.");
  }

  await fs.remove(RELEASE_DIR);
  await fs.ensureDir(SOURCE_DIR);
  await copyProjectToSource();
  await prepareDeployPlaceholder();

  const manifest = await listTopLevelEntries();
  const manifestContent = {
    version,
    generatedAt: new Date().toISOString(),
    files: manifest.files,
    requiresInstall: manifest.requiresInstall,
  };

  manifestContent.files.push({
    path: "update-manifest.json",
    type: "file",
  });

  await fs.writeJson(
    path.join(SOURCE_DIR, "update-manifest.json"),
    manifestContent,
    { spaces: 2 }
  );

  await fs.ensureDir(ARTIFACTS_DIR);
  const zipFileName = `${APP_NAME}-v${version}.zip`;
  const zipPath = path.join(ARTIFACTS_DIR, zipFileName);
  writeZipFromSource(zipPath);

  const checksum = await fileSha256(zipPath);
  const { size } = await fs.stat(zipPath);

  await fs.ensureDir(RELEASES_DIR);
  await fs.copy(zipPath, path.join(RELEASES_DIR, zipFileName));
  await fs.copy(zipPath, path.join(RELEASES_DIR, "latest.zip"));

  const versionManifest = {
    version,
    downloadUrl: `${baseUrl}/releases/${zipFileName}`,
    checksum: `sha256-${checksum}`,
    releaseDate: new Date().toISOString(),
    size,
    requiresInstall: manifestContent.requiresInstall,
    files: manifestContent.files,
  };

  await fs.writeJson(path.join(SERVER_DIR, "version.json"), versionManifest, {
    spaces: 2,
  });
  await fs.writeFile(
    path.join(SERVER_DIR, "index.html"),
    createIndexHtml(versionManifest),
    "utf8"
  );

  await fs.remove(CAPROVER_CONTEXT_DIR);
  await fs.ensureDir(path.join(CAPROVER_CONTEXT_DIR, "deploy"));
  await fs.copy(
    SERVER_DIR,
    path.join(CAPROVER_CONTEXT_DIR, "deploy", "server")
  );
  await fs.copy(
    path.join(ROOT_DIR, "Dockerfile"),
    path.join(CAPROVER_CONTEXT_DIR, "Dockerfile")
  );

  console.log(
    `[buildRelease] Release ${version} ready. Artifact: ${zipPath.replace(
      ROOT_DIR,
      "."
    )}`
  );

  return {
    version,
    zipPath,
    checksum,
    versionManifest,
    caproverContextDir: CAPROVER_CONTEXT_DIR,
  };
}

if (require.main === module) {
  buildRelease().catch((error) => {
    console.error("[buildRelease] Failed:", error);
    process.exitCode = 1;
  });
}

module.exports = {
  buildRelease,
};
