<?php
declare(strict_types=1);

namespace Itdatex\EInvoiceCore;

/**
 * Datei-Layer mit SHA-256-Hashkette.
 *
 * Verantwortlich fuer:
 *   - Speichern der Datei mit Zufallsnamen auf der Platte (Original-Name nur in DB)
 *   - Berechnen des Datei-SHA-256
 *   - Verketten mit dem chain_hash der vorherigen Zeile
 *
 * Aufrufer reicht den Upload-Pfad via Konstruktor rein — die Lib weiss
 * nichts ueber WP-Optionen oder Plugin-Installer.
 *
 * Die Tabelle selbst wird via InvoiceRepository::insertFile geschrieben — Storage
 * gibt nur das vorbereitete Datensatz-Array zurueck (Single Responsibility).
 */
final class Storage
{
    public function __construct( private readonly string $uploadDir ) {}

    /**
     * @return array{stored_name:string, original_name:string, mime:string, size_bytes:int, sha256:string, chain_hash:string, full_path:string}
     */
    public function persist( string $sourcePath, string $originalName, string $previousChainHash ): array
    {
        if ( ! is_file( $sourcePath ) || ! is_readable( $sourcePath ) ) {
            throw new \RuntimeException( 'Datei nicht lesbar: ' . esc_html( $sourcePath ) );
        }

        $sha256 = hash_file( 'sha256', $sourcePath );
        if ( ! is_string( $sha256 ) || strlen( $sha256 ) !== 64 ) {
            throw new \RuntimeException( 'SHA-256 konnte nicht berechnet werden.' );
        }

        $dir = $this->uploadDir;
        if ( $dir === '' || ! is_dir( $dir ) || ! wp_is_writable( $dir ) ) {
            throw new \RuntimeException( 'Upload-Ordner nicht beschreibbar: ' . esc_html( $dir ) );
        }

        // Extension aus dem Originalnamen extrahieren — beschraenkt auf bekannte Formate
        $ext = self::safeExtension( $originalName );

        // Zufallsname
        $storedName = bin2hex( random_bytes( 16 ) ) . ( $ext ? ".{$ext}" : '' );
        $fullPath   = rtrim( $dir, '/' ) . '/' . $storedName;

        if ( ! copy( $sourcePath, $fullPath ) ) {
            throw new \RuntimeException( 'Datei konnte nicht gespeichert werden: ' . esc_html( $fullPath ) );
        }

        $mime = self::detectMime( $fullPath );
        $size = (int) filesize( $fullPath );

        // Hashkette: SHA256( previousChainHash . currentSha256 )
        // Erste Datei: previousChainHash = 64x '0'
        $prev = $previousChainHash !== '' ? $previousChainHash : str_repeat( '0', 64 );
        $chainHash = hash( 'sha256', $prev . $sha256 );

        return [
            'stored_name'   => $storedName,
            'original_name' => $originalName,
            'mime'          => $mime,
            'size_bytes'    => $size,
            'sha256'        => $sha256,
            'chain_hash'    => $chainHash,
            'full_path'     => $fullPath,
        ];
    }

    public function fullPath( string $storedName ): string
    {
        return rtrim( $this->uploadDir, '/' ) . '/' . $storedName;
    }

    private static function safeExtension( string $original ): string
    {
        $ext = strtolower( (string) pathinfo( $original, PATHINFO_EXTENSION ) );
        return in_array( $ext, [ 'xml', 'pdf' ], true ) ? $ext : '';
    }

    private static function detectMime( string $path ): string
    {
        if ( function_exists( 'finfo_open' ) ) {
            $f = finfo_open( FILEINFO_MIME_TYPE );
            if ( $f !== false ) {
                $mime = (string) finfo_file( $f, $path );
                finfo_close( $f );
                if ( $mime !== '' ) return $mime;
            }
        }
        return 'application/octet-stream';
    }
}
