<?php
declare(strict_types=1);

function fail(string $message): never
{
    fwrite(STDERR, $message . PHP_EOL);
    exit(1);
}

function canonicalize(mixed $value): mixed
{
    if (!is_array($value)) {
        return $value;
    }

    if (array_is_list($value)) {
        return array_map('canonicalize', $value);
    }

    ksort($value, SORT_STRING);
    foreach ($value as $key => $item) {
        $value[$key] = canonicalize($item);
    }

    return $value;
}

function loadSecretKey(string $path): string
{
    if (!is_file($path)) {
        fail('Clé privée absente : ' . $path);
    }

    $raw = (string) file_get_contents($path);

    if (strlen($raw) === SODIUM_CRYPTO_SIGN_SECRETKEYBYTES) {
        return $raw;
    }

    $decoded = base64_decode(trim($raw), true);

    if (is_string($decoded) && strlen($decoded) === SODIUM_CRYPTO_SIGN_SECRETKEYBYTES) {
        return $decoded;
    }

    if (is_string($decoded) && strlen($decoded) === SODIUM_CRYPTO_SIGN_SEEDBYTES) {
        return sodium_crypto_sign_secretkey(
            sodium_crypto_sign_seed_keypair($decoded)
        );
    }

    if (strlen($raw) === SODIUM_CRYPTO_SIGN_SEEDBYTES) {
        return sodium_crypto_sign_secretkey(
            sodium_crypto_sign_seed_keypair($raw)
        );
    }

    fail('Format de clé privée Ed25519 non reconnu.');
}

if (PHP_SAPI !== 'cli') {
    fail('Exécution en ligne de commande obligatoire.');
}

if (!extension_loaded('sodium') || !class_exists(ZipArchive::class)) {
    fail('Extensions Sodium et ZipArchive obligatoires.');
}

$builderDirectory = __DIR__;
$sourceDirectory = $builderDirectory . DIRECTORY_SEPARATOR . 'source';
$projectRoot = $argv[1] ?? dirname($builderDirectory);
$projectRoot = rtrim((string) $projectRoot, "\\/");

$keyPath = $projectRoot
    . DIRECTORY_SEPARATOR . 'storage'
    . DIRECTORY_SEPARATOR . 'private'
    . DIRECTORY_SEPARATOR . 'update_signing_secret.key';

$secretKey = loadSecretKey($keyPath);

$manifestPath = $sourceDirectory . DIRECTORY_SEPARATOR . 'manifest.json';
$manifest = json_decode((string) file_get_contents($manifestPath), true);

if (!is_array($manifest)) {
    fail('manifest.json invalide.');
}

$files = [];
$iterator = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($sourceDirectory, FilesystemIterator::SKIP_DOTS)
);

foreach ($iterator as $fileInfo) {
    if (!$fileInfo->isFile()) {
        continue;
    }

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

    if ($relativePath === 'manifest.json') {
        continue;
    }

    $files[] = [
        'path' => $relativePath,
        'sha256' => hash_file('sha256', $absolutePath),
    ];
}

usort(
    $files,
    static fn(array $left, array $right): int =>
        strcmp($left['path'], $right['path'])
);

$manifest['files'] = $files;
unset($manifest['signature']);

$canonical = json_encode(
    canonicalize($manifest),
    JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);

if (!is_string($canonical)) {
    fail('Canonicalisation du manifeste impossible.');
}

$manifest['signature'] = base64_encode(
    sodium_crypto_sign_detached($canonical, $secretKey)
);

$encodedManifest = json_encode(
    $manifest,
    JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);

if (!is_string($encodedManifest)) {
    fail('Encodage du manifeste impossible.');
}

file_put_contents($manifestPath, $encodedManifest . PHP_EOL);

$outputPath = $builderDirectory
    . DIRECTORY_SEPARATOR
    . 'CDA_AXIOM_PLATFORM_023_3_5_2_VALIDATION_CENTER_MYSQL_FIX_10.2.4.2_SIGNED_R1.zip';

@unlink($outputPath);

$zip = new ZipArchive();
if ($zip->open($outputPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
    fail('Création du ZIP impossible.');
}

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

foreach ($iterator as $fileInfo) {
    if (!$fileInfo->isFile()) {
        continue;
    }

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

    $zip->addFile($absolutePath, $relativePath);
}

$zip->close();

echo 'Package généré :' . PHP_EOL;
echo $outputPath . PHP_EOL;
