<?php

declare(strict_types=1);

namespace App\Services;

$signatureService = new SignatureService();

final class BuildService
{
    /**
     * Construit un package AXIOM.
     */
    public function build(array $configuration = []): array
    {
        $buildId = 'BUILD-' . date('Ymd-His');

        $buildRoot = BASE_PATH . '/storage/builds';
        $buildPath = $buildRoot . '/' . $buildId;

        if (!is_dir($buildRoot)) {
            mkdir($buildRoot, 0775, true);
        }

        mkdir($buildPath, 0775, true);
        mkdir($buildPath . '/package', 0775, true);
        mkdir($buildPath . '/logs', 0775, true);
        mkdir($buildPath . '/reports', 0775, true);

        /*
        |--------------------------------------------------------------------------
        | Journal
        |--------------------------------------------------------------------------
        */

        $started = microtime(true);

        $log = [];

        $log[] = '=== AXIOM BUILD ===';
        $log[] = 'Build : ' . $buildId;
        $log[] = 'Date  : ' . date('Y-m-d H:i:s');
        $log[] = '';

        /*
        |--------------------------------------------------------------------------
        | Version
        |--------------------------------------------------------------------------
        */

        $version = is_file(BASE_PATH . '/VERSION')
            ? trim((string) file_get_contents(BASE_PATH . '/VERSION'))
            : 'unknown';

        /*
        |--------------------------------------------------------------------------
        | Politique de build
        |--------------------------------------------------------------------------
        */

        $policy = require BASE_PATH . '/app/Config/BuildPolicy.php';

        $items = $policy['include'] ?? [];
        $excluded = $policy['exclude'] ?? [];

        $log[] = 'Copie des éléments :';

        foreach ($items as $item) {

            if (in_array($item, $excluded, true)) {
                continue;
            }

            $source = BASE_PATH . DIRECTORY_SEPARATOR . $item;
            $destination = $buildPath . '/package/' . $item;

            $this->copy(
                $source,
                $destination,
                $excluded
            );

            $log[] = '  ✓ ' . $item;
        }

        /*
        |--------------------------------------------------------------------------
        | Hash des fichiers
        |--------------------------------------------------------------------------
        */

        $files = $this->collectHashes(
            $buildPath . '/package'
        );

        /*
        |--------------------------------------------------------------------------
        | Création du ZIP
        |--------------------------------------------------------------------------
        */

        $zipFile = sprintf(
            '%s/AXIOM_UPDATE_%s_%s.zip',
            $buildPath,
            $version,
            $buildId
        );

        $zip = new \ZipArchive();

        if (
            $zip->open(
                $zipFile,
                \ZipArchive::CREATE |
                \ZipArchive::OVERWRITE
            ) !== true
        ) {
            throw new \RuntimeException(
                'Impossible de créer le package ZIP.'
            );
        }

        $this->zipDirectory(
            $buildPath . '/package',
            $zip
        );

        $zip->close();

        /*
        |--------------------------------------------------------------------------
        | Hash du ZIP
        |--------------------------------------------------------------------------
        */

        $package = [

            'filename' => basename($zipFile),

            'size' => filesize($zipFile),

            'sha256' => hash_file(
                'sha256',
                $zipFile
            ),

        ];

        /*
        |--------------------------------------------------------------------------
        | Manifest
        |--------------------------------------------------------------------------
        */

        $manifest = [

            'build' => $buildId,

            'version' => $version,

            'status' => 'created',

            'created_at' => date('c'),

            'configuration' => $configuration,

            'package' => $package,

            'files' => $files,

        ];

        $this->writeManifest(
    $buildPath . '/manifest.json',
    $manifest
);

$signatureService = new SignatureService();

$signature = $signatureService->sign(
    $buildPath . '/manifest.json'
);

file_put_contents(
    $buildPath . '/manifest.sig',
    $signature
);

$log[] = 'Manifest signé (Ed25519).';

        /*
        |--------------------------------------------------------------------------
        | Journal
        |--------------------------------------------------------------------------
        */

        $log[] = '';
        $log[] = 'ZIP créé avec succès.';
        $log[] = '';

        $log[] = sprintf(

            'Durée : %.3f secondes',

            microtime(true) - $started

        );

        $this->writeLog(
    $buildPath . '/logs/build.log',
    $log
);

        return [

            'success' => true,

            'status' => 'created',

            'build' => $buildId,

            'message' => 'Construction initialisée : ' . $buildId,

            'configuration' => $configuration,

            'started_at' => date('Y-m-d H:i:s'),

        ];
    }
        /**
     * Copie récursive avec politique d'exclusion.
     */
    private function copy(
        string $source,
        string $destination,
        array $excluded = []
    ): void {

        $relative = str_replace(
            '\\',
            '/',
            substr(
                $source,
                strlen(BASE_PATH) + 1
            )
        );

        foreach ($excluded as $exclude) {

            $exclude = trim(
                str_replace('\\', '/', $exclude),
                '/'
            );

            if (
                $relative === $exclude ||
                str_starts_with(
                    $relative,
                    $exclude . '/'
                )
            ) {
                return;
            }
        }

        if (is_file($source)) {

            $directory = dirname($destination);

            if (!is_dir($directory)) {
                mkdir(
                    $directory,
                    0775,
                    true
                );
            }

            copy(
                $source,
                $destination
            );

            return;
        }

        if (!is_dir($source)) {
            return;
        }

        if (!is_dir($destination)) {

            mkdir(
                $destination,
                0775,
                true
            );
        }

        foreach (scandir($source) as $file) {

            if (
                $file === '.' ||
                $file === '..'
            ) {
                continue;
            }

            $this->copy(

                $source .
                DIRECTORY_SEPARATOR .
                $file,

                $destination .
                DIRECTORY_SEPARATOR .
                $file,

                $excluded

            );
        }
    }

    /**
     * Ajoute récursivement un dossier dans le ZIP.
     */
    private function zipDirectory(
        string $directory,
        \ZipArchive $zip,
        string $inside = ''
    ): void {

        foreach (scandir($directory) as $file) {

            if (
                $file === '.' ||
                $file === '..'
            ) {
                continue;
            }

            $path =
                $directory .
                DIRECTORY_SEPARATOR .
                $file;

            $entry = ltrim(
                $inside .
                '/' .
                $file,
                '/'
            );

            if (is_dir($path)) {

                $zip->addEmptyDir(
                    $entry
                );

                $this->zipDirectory(
                    $path,
                    $zip,
                    $entry
                );

            } else {

                $zip->addFile(
                    $path,
                    $entry
                );

            }
        }
    }
        /**
     * Calcule les empreintes SHA-256 des fichiers du package.
     */
    private function collectHashes(string $directory): array
    {
        $files = [];

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

        foreach ($iterator as $file) {

            if (!$file->isFile()) {
                continue;
            }

            $absolute = $file->getPathname();

            $relative = str_replace(
                '\\',
                '/',
                substr(
                    $absolute,
                    strlen($directory) + 1
                )
            );

            $files[$relative] = [

                'size' => filesize($absolute),

                'sha256' => hash_file(
                    'sha256',
                    $absolute
                ),

                'modified_at' => date(
                    'c',
                    filemtime($absolute)
                ),

            ];
        }

        ksort($files);

        return $files;
    }

    /**
     * Écrit le manifest au format JSON.
     */
    private function writeManifest(
        string $filename,
        array $manifest
    ): void {

        file_put_contents(

            $filename,

            json_encode(

                $manifest,

                JSON_PRETTY_PRINT
                | JSON_UNESCAPED_SLASHES

            )

        );
    }

    /**
     * Écrit le journal du build.
     */
    private function writeLog(
        string $filename,
        array $lines
    ): void {

        file_put_contents(

            $filename,

            implode(
                PHP_EOL,
                $lines
            )

        );
    }
}