<?php
/**
 * Create a ZIP archive from a folder and split it into download-sized parts.
 *
 * Best run in cPanel Terminal / SSH, not through a browser:
 *   php split_folder_zip.php
 *
 * Requires: PHP CLI, ZipArchive extension, and the `split` command.
 * The destination directory must have enough free space for the ZIP archive.
 */

declare(strict_types=1);
set_time_limit(0);
ignore_user_abort(true);

// EDIT THESE SETTINGS:
const SOURCE_FOLDER = __DIR__ . '/artists';
const OUTPUT_FOLDER = __DIR__ . '/split-zip-output';
const ARCHIVE_NAME = 'backup.zip';
const PART_SIZE_MB = 3072; // 1024 MB = approximately 1 GiB per part

function logLine(string $message): void
{
    echo '[' . date('Y-m-d H:i:s') . '] ' . $message . PHP_EOL;
}

if (PHP_SAPI !== 'cli') {
    http_response_code(403);
    exit("Run this script from cPanel Terminal / SSH, not from a browser.\n");
}

if (!class_exists(ZipArchive::class)) {
    exit("ERROR: PHP ZipArchive extension is not enabled.\n");
}

if (!function_exists('exec')) {
    exit("ERROR: exec() is disabled. Use cPanel Terminal commands instead.\n");
}

if (!is_dir(SOURCE_FOLDER)) {
    exit("ERROR: Source folder not found: " . SOURCE_FOLDER . "\n");
}

if (!is_dir(OUTPUT_FOLDER) && !mkdir(OUTPUT_FOLDER, 0750, true)) {
    exit("ERROR: Could not create output directory.\n");
}

$sourceReal = realpath(SOURCE_FOLDER);
$outputReal = realpath(OUTPUT_FOLDER);
if ($sourceReal === false || $outputReal === false) {
    exit("ERROR: Could not resolve source/output folder paths.\n");
}
if ($outputReal === $sourceReal || str_starts_with($outputReal . DIRECTORY_SEPARATOR, $sourceReal . DIRECTORY_SEPARATOR)) {
    exit("ERROR: Output folder must not be inside the source folder.\n");
}

$archivePath = $outputReal . DIRECTORY_SEPARATOR . ARCHIVE_NAME;
$partBytes = PART_SIZE_MB * 1024 * 1024;

logLine("Source folder: {$sourceReal}");
logLine("Output archive: {$archivePath}");
logLine("Part size: " . PART_SIZE_MB . " MiB");

// Remove an earlier archive to avoid accidentally using stale data.
if (is_file($archivePath) && !unlink($archivePath)) {
    exit("ERROR: Could not remove existing archive: {$archivePath}\n");
}

$zip = new ZipArchive();
$openResult = $zip->open($archivePath, ZipArchive::CREATE | ZipArchive::OVERWRITE);
if ($openResult !== true) {
    exit("ERROR: Could not create ZIP archive. ZipArchive error code: {$openResult}\n");
}

$iterator = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($sourceReal, FilesystemIterator::SKIP_DOTS),
    RecursiveIteratorIterator::LEAVES_ONLY
);

$count = 0;
foreach ($iterator as $fileInfo) {
    if (!$fileInfo->isFile() || $fileInfo->isLink()) {
        continue;
    }

    $absolutePath = $fileInfo->getPathname();
    $relativePath = substr($absolutePath, strlen($sourceReal) + 1);
    $relativePath = str_replace(DIRECTORY_SEPARATOR, '/', $relativePath);

    if (!$zip->addFile($absolutePath, $relativePath)) {
        $zip->close();
        exit("ERROR: Failed to add file: {$relativePath}\n");
    }

    $count++;
    if ($count % 100 === 0) {
        logLine("Added {$count} files...");
    }
}

if (!$zip->close()) {
    exit("ERROR: Failed to finalize ZIP archive.\n");
}

$archiveSize = filesize($archivePath);
if ($archiveSize === false) {
    exit("ERROR: Could not read archive size.\n");
}
logLine("ZIP created with {$count} files; size: " . number_format($archiveSize) . " bytes.");

// Use the system `split` utility. Names become backup.zip.part-000, part-001, etc.
$prefix = $outputReal . DIRECTORY_SEPARATOR . ARCHIVE_NAME . '.part-';
$command = 'split -b ' . escapeshellarg((string)$partBytes)
    . ' -d -a 3 '
    . escapeshellarg($archivePath) . ' '
    . escapeshellarg($prefix)
    . ' 2>&1';

$output = [];
$status = 0;
exec($command, $output, $status);
if ($status !== 0) {
    logLine("ERROR: split command failed:");
    foreach ($output as $line) {
        logLine($line);
    }
    exit(1);
}

$parts = glob($prefix . '*') ?: [];
sort($parts, SORT_STRING);
logLine("Created " . count($parts) . " part(s):");
foreach ($parts as $part) {
    logLine(basename($part) . ' — ' . number_format((int)filesize($part)) . ' bytes');
}

logLine("DONE. Keep every part. To reconstruct, concatenate the parts in order, then test/unzip the reconstructed ZIP.");
logLine("Example restore command:");
logLine('cat ' . escapeshellarg(basename($archivePath) . '.part-*') . ' > ' . escapeshellarg('restored.zip'));
logLine('unzip -t ' . escapeshellarg('restored.zip'));
