<?php

declare(strict_types=1);

namespace SecurWP\Modules\Scanner\Service;

use SecurWP\Includes\GuardedDataFile;
use SecurWP\Includes\MasterKey;

if ( ! defined( 'ABSPATH' ) ) { exit; }

class QuarantineCipher {

    private const CHUNK_BYTES = 65536;
    private const MAGIC_SODIUM = 'SWPQS1';
    private const MAGIC_OPENSSL = 'SWPQO1';
    private const OPENSSL_SALT_BYTES = 16;
    private const OPENSSL_IV_BYTES = 16;
    private const OPENSSL_MAC_BYTES = 32;
    private const SECRETSTREAM_HEADER_BYTES = 24;
    private const PROVIDER_SODIUM = 'sodium';
    private const PROVIDER_OPENSSL = 'openssl';

    /**
     * @return array<string,mixed>|\WP_Error
     */
    public static function encrypt_file( string $source_path, string $dest_path ) {
        if ( ! is_file( $source_path ) || ! is_readable( $source_path ) ) {
            return new \WP_Error( 'quarantine_source_unreadable', 'The selected file could not be read for quarantine.' );
        }

        $destination_dir = dirname( $dest_path );
        if ( ! is_dir( $destination_dir ) && ! wp_mkdir_p( $destination_dir ) ) {
            return new \WP_Error( 'quarantine_destination_unwritable', 'SecurWP could not create the quarantine storage directory.' );
        }

        $source = @fopen( $source_path, 'rb' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Binary stream access is required for chunked encryption.
        if ( false === $source ) {
            return new \WP_Error( 'quarantine_source_open_failed', 'SecurWP could not open the file for quarantine.' );
        }

        $dest = @fopen( $dest_path, 'wb' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Binary stream access is required for chunked encryption.
        if ( false === $dest ) {
            @fclose( $source ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
            return new \WP_Error( 'quarantine_destination_open_failed', 'SecurWP could not create the encrypted quarantine file.' );
        }

        try {
            $result = self::encrypt_with_best_provider( $source, $dest );
        } catch ( \Throwable $e ) {
            $result = new \WP_Error( 'quarantine_encrypt_failed', 'SecurWP could not encrypt the quarantine file.' );
            securwp_log( '[SecurWP] Quarantine encryption failed: ' . sanitize_text_field( $e->getMessage() ) );
        }

        @fclose( $source ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
        @fclose( $dest ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose

        if ( is_wp_error( $result ) ) {
            @unlink( $dest_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
        }

        return $result;
    }

    /**
     * @return array<string,mixed>|\WP_Error
     */
    public static function decrypt_file( string $source_path, string $dest_path ) {
        if ( ! is_file( $source_path ) || ! is_readable( $source_path ) ) {
            return new \WP_Error( 'quarantine_blob_missing', 'The encrypted quarantine blob is missing or unreadable.' );
        }

        $destination_dir = dirname( $dest_path );
        if ( ! is_dir( $destination_dir ) && ! wp_mkdir_p( $destination_dir ) ) {
            return new \WP_Error( 'quarantine_restore_directory_failed', 'SecurWP could not prepare the destination directory.' );
        }

        $source = @fopen( $source_path, 'rb' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Binary stream access is required for chunked decryption.
        if ( false === $source ) {
            return new \WP_Error( 'quarantine_blob_open_failed', 'SecurWP could not open the encrypted quarantine blob.' );
        }

        if ( ! self::seek_to_payload_start( $source, $source_path ) ) {
            @fclose( $source ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
            return new \WP_Error( 'quarantine_blob_invalid', 'The encrypted quarantine blob is incomplete.' );
        }

        $dest = @fopen( $dest_path, 'wb' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Binary stream access is required for chunked decryption.
        if ( false === $dest ) {
            @fclose( $source ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
            return new \WP_Error( 'quarantine_restore_open_failed', 'SecurWP could not create the decrypted output file.' );
        }

        try {
            $magic = self::read_exact( $source, 6 );
            if ( false === $magic ) {
                $result = new \WP_Error( 'quarantine_blob_invalid', 'The encrypted quarantine blob is incomplete.' );
            } elseif ( $magic === self::MAGIC_SODIUM ) {
                $result = self::decrypt_with_sodium( $source, $dest );
            } elseif ( $magic === self::MAGIC_OPENSSL ) {
                $result = self::decrypt_with_openssl( $source, $dest, $source_path );
            } else {
                $result = new \WP_Error( 'quarantine_blob_unknown_format', 'The encrypted quarantine blob format is not recognized.' );
            }
        } catch ( \Throwable $e ) {
            $result = new \WP_Error( 'quarantine_decrypt_failed', 'SecurWP could not decrypt the quarantine file.' );
            securwp_log( '[SecurWP] Quarantine decryption failed: ' . sanitize_text_field( $e->getMessage() ) );
        }

        @fclose( $source ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
        @fclose( $dest ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose

        if ( is_wp_error( $result ) ) {
            @unlink( $dest_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
        }

        return $result;
    }

    /**
     * @param resource $source
     * @param resource $dest
     * @return array<string,mixed>|\WP_Error
     */
    private static function encrypt_with_sodium( $source, $dest ) {
        $key = self::derive_key( '', 'sodium' );
        $state = sodium_crypto_secretstream_xchacha20poly1305_init_push( $key );
        self::memzero( $key );

        if ( ! is_array( $state ) || count( $state ) !== 2 ) {
            return new \WP_Error( 'quarantine_sodium_init_failed', 'SecurWP could not initialize sodium streaming encryption.' );
        }

        [ $stream_state, $header ] = $state;

        if ( ! self::write_all( $dest, self::MAGIC_SODIUM . $header ) ) {
            return new \WP_Error( 'quarantine_blob_write_failed', 'SecurWP could not write the encrypted quarantine header.' );
        }

        $sha = hash_init( 'sha256' );
        $size = 0;

        $pending = fread( $source, self::CHUNK_BYTES ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread
        if ( false === $pending ) {
            return new \WP_Error( 'quarantine_source_read_failed', 'SecurWP could not read the file for encryption.' );
        }

        while ( $pending !== '' ) {
            $chunk = $pending;
            $pending = fread( $source, self::CHUNK_BYTES ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread
            if ( false === $pending ) {
                return new \WP_Error( 'quarantine_source_read_failed', 'SecurWP could not finish reading the file for encryption.' );
            }

            $size += strlen( $chunk );
            hash_update( $sha, $chunk );

            $tag = ( $pending === '' )
                ? SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL
                : SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE;

            $ciphertext = sodium_crypto_secretstream_xchacha20poly1305_push( $stream_state, $chunk, '', $tag );
            if ( false === $ciphertext ) {
                return new \WP_Error( 'quarantine_encrypt_failed', 'SecurWP could not encrypt the quarantine payload.' );
            }

            $frame = pack( 'N', strlen( $ciphertext ) ) . $ciphertext;
            if ( ! self::write_all( $dest, $frame ) ) {
                return new \WP_Error( 'quarantine_blob_write_failed', 'SecurWP could not write the encrypted quarantine payload.' );
            }
        }

        return [
            'provider' => 'sodium',
            'sha256'   => hash_final( $sha ),
            'size'     => $size,
        ];
    }

    /**
     * @param resource $source
     * @param resource $dest
     * @return array<string,mixed>|\WP_Error
     */
    private static function encrypt_with_openssl( $source, $dest ) {
        $salt = random_bytes( self::OPENSSL_SALT_BYTES );
        $base_iv = random_bytes( self::OPENSSL_IV_BYTES );
        $enc_key = self::derive_key( $salt, 'openssl-enc' );
        $mac_key = self::derive_key( $salt, 'openssl-mac' );
        $counter_iv = $base_iv;

        $header = self::MAGIC_OPENSSL . $salt . $base_iv;
        if ( ! self::write_all( $dest, $header ) ) {
            self::memzero( $enc_key );
            self::memzero( $mac_key );
            return new \WP_Error( 'quarantine_blob_write_failed', 'SecurWP could not write the encrypted quarantine header.' );
        }

        $hmac = hash_init( 'sha256', HASH_HMAC, $mac_key );
        hash_update( $hmac, $header );

        $sha = hash_init( 'sha256' );
        $size = 0;

        while ( ! feof( $source ) ) {
            $chunk = fread( $source, self::CHUNK_BYTES ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread
            if ( false === $chunk ) {
                self::memzero( $enc_key );
                self::memzero( $mac_key );
                return new \WP_Error( 'quarantine_source_read_failed', 'SecurWP could not read the file for encryption.' );
            }

            if ( '' === $chunk ) {
                continue;
            }

            $ciphertext = openssl_encrypt(
                $chunk,
                'aes-256-ctr',
                $enc_key,
                OPENSSL_RAW_DATA,
                $counter_iv
            );
            if ( false === $ciphertext ) {
                self::memzero( $enc_key );
                self::memzero( $mac_key );
                return new \WP_Error( 'quarantine_encrypt_failed', 'SecurWP could not encrypt the quarantine payload.' );
            }

            hash_update( $hmac, $ciphertext );
            hash_update( $sha, $chunk );
            $size += strlen( $chunk );
            $counter_iv = self::increment_ctr_iv( $counter_iv, (int) ceil( strlen( $chunk ) / 16 ) );

            if ( ! self::write_all( $dest, $ciphertext ) ) {
                self::memzero( $enc_key );
                self::memzero( $mac_key );
                return new \WP_Error( 'quarantine_blob_write_failed', 'SecurWP could not write the encrypted quarantine payload.' );
            }
        }

        $mac = hash_final( $hmac, true );
        self::memzero( $enc_key );
        self::memzero( $mac_key );

        if ( ! self::write_all( $dest, $mac ) ) {
            return new \WP_Error( 'quarantine_blob_write_failed', 'SecurWP could not finalize the encrypted quarantine payload.' );
        }

        return [
            'provider' => 'openssl',
            'sha256'   => hash_final( $sha ),
            'size'     => $size,
        ];
    }

    /**
     * @param resource $source
     * @param resource $dest
     * @return array<string,mixed>|\WP_Error
     */
    private static function decrypt_with_sodium( $source, $dest ) {
        if ( ! self::supports_any_sodium_secretstream() ) {
            return new \WP_Error( 'quarantine_sodium_missing', 'Sodium support is required to decrypt this quarantine file.' );
        }

        $header = self::read_exact( $source, self::SECRETSTREAM_HEADER_BYTES );
        if ( false === $header ) {
            return new \WP_Error( 'quarantine_blob_invalid', 'The encrypted quarantine header is incomplete.' );
        }

        $key = self::derive_key( '', 'sodium' );
        $stream_state = sodium_crypto_secretstream_xchacha20poly1305_init_pull( $header, $key );
        self::memzero( $key );

        if ( false === $stream_state ) {
            return new \WP_Error( 'quarantine_decrypt_failed', 'SecurWP could not initialize sodium streaming decryption.' );
        }

        $sha = hash_init( 'sha256' );
        $size = 0;
        $saw_final = false;

        while ( ! feof( $source ) ) {
            $len_raw = fread( $source, 4 ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread
            if ( false === $len_raw ) {
                return new \WP_Error( 'quarantine_blob_read_failed', 'SecurWP could not read the encrypted quarantine frame size.' );
            }

            if ( '' === $len_raw ) {
                break;
            }

            if ( strlen( $len_raw ) !== 4 ) {
                return new \WP_Error( 'quarantine_blob_invalid', 'The encrypted quarantine frame size is incomplete.' );
            }

            $frame_len = unpack( 'Nlen', $len_raw );
            $payload_len = (int) ( $frame_len['len'] ?? 0 );
            if ( $payload_len <= 0 ) {
                return new \WP_Error( 'quarantine_blob_invalid', 'The encrypted quarantine frame length is invalid.' );
            }

            $ciphertext = self::read_exact( $source, $payload_len );
            if ( false === $ciphertext ) {
                return new \WP_Error( 'quarantine_blob_invalid', 'The encrypted quarantine frame is incomplete.' );
            }

            $pulled = sodium_crypto_secretstream_xchacha20poly1305_pull( $stream_state, $ciphertext );
            if ( false === $pulled || ! is_array( $pulled ) || count( $pulled ) !== 2 ) {
                return new \WP_Error( 'quarantine_decrypt_failed', 'SecurWP could not authenticate the quarantine file.' );
            }

            [ $plaintext, $tag ] = $pulled;
            $size += strlen( $plaintext );
            hash_update( $sha, $plaintext );

            if ( ! self::write_all( $dest, $plaintext ) ) {
                return new \WP_Error( 'quarantine_restore_write_failed', 'SecurWP could not write the decrypted quarantine payload.' );
            }

            if ( $tag === SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL ) {
                $saw_final = true;
            }
        }

        if ( ! $saw_final ) {
            return new \WP_Error( 'quarantine_blob_invalid', 'The encrypted quarantine file ended before the final authentication frame.' );
        }

        return [
            'provider' => 'sodium',
            'sha256'   => hash_final( $sha ),
            'size'     => $size,
        ];
    }

    /**
     * @param resource $source
     * @param resource $dest
     * @return array<string,mixed>|\WP_Error
     */
    private static function decrypt_with_openssl( $source, $dest, string $source_path ) {
        $header_rest = self::read_exact( $source, self::OPENSSL_SALT_BYTES + self::OPENSSL_IV_BYTES );
        if ( false === $header_rest ) {
            return new \WP_Error( 'quarantine_blob_invalid', 'The encrypted quarantine header is incomplete.' );
        }

        $salt = substr( $header_rest, 0, self::OPENSSL_SALT_BYTES );
        $base_iv = substr( $header_rest, self::OPENSSL_SALT_BYTES, self::OPENSSL_IV_BYTES );
        $enc_key = self::derive_key( $salt, 'openssl-enc' );
        $mac_key = self::derive_key( $salt, 'openssl-mac' );
        $counter_iv = $base_iv;

        $raw_size       = @filesize( $source_path );
        $payload_offset = GuardedDataFile::payload_offset_for_path( $source_path );
        if ( false === $raw_size ) {
            self::memzero( $enc_key );
            self::memzero( $mac_key );
            return new \WP_Error( 'quarantine_blob_invalid', 'SecurWP could not determine the encrypted blob size.' );
        }

        $blob_size = max( 0, (int) $raw_size - $payload_offset );
        $payload_size = $blob_size - 6 - self::OPENSSL_SALT_BYTES - self::OPENSSL_IV_BYTES - self::OPENSSL_MAC_BYTES;
        if ( $payload_size < 0 ) {
            self::memzero( $enc_key );
            self::memzero( $mac_key );
            return new \WP_Error( 'quarantine_blob_invalid', 'The encrypted quarantine blob is truncated.' );
        }

        $hmac = hash_init( 'sha256', HASH_HMAC, $mac_key );
        hash_update( $hmac, self::MAGIC_OPENSSL . $header_rest );

        $sha = hash_init( 'sha256' );
        $size = 0;
        $remaining = $payload_size;

        while ( $remaining > 0 ) {
            $to_read = min( self::CHUNK_BYTES, $remaining );
            $ciphertext = self::read_exact( $source, $to_read );
            if ( false === $ciphertext ) {
                self::memzero( $enc_key );
                self::memzero( $mac_key );
                return new \WP_Error( 'quarantine_blob_invalid', 'The encrypted quarantine payload is truncated.' );
            }

            hash_update( $hmac, $ciphertext );
            $remaining -= strlen( $ciphertext );

            $plaintext = openssl_decrypt(
                $ciphertext,
                'aes-256-ctr',
                $enc_key,
                OPENSSL_RAW_DATA,
                $counter_iv
            );
            if ( false === $plaintext ) {
                self::memzero( $enc_key );
                self::memzero( $mac_key );
                return new \WP_Error( 'quarantine_decrypt_failed', 'SecurWP could not decrypt the quarantine payload.' );
            }

            $counter_iv = self::increment_ctr_iv( $counter_iv, (int) ceil( strlen( $plaintext ) / 16 ) );
            $size += strlen( $plaintext );
            hash_update( $sha, $plaintext );

            if ( ! self::write_all( $dest, $plaintext ) ) {
                self::memzero( $enc_key );
                self::memzero( $mac_key );
                return new \WP_Error( 'quarantine_restore_write_failed', 'SecurWP could not write the decrypted quarantine payload.' );
            }
        }

        $expected_mac = self::read_exact( $source, self::OPENSSL_MAC_BYTES );
        $actual_mac = hash_final( $hmac, true );
        self::memzero( $enc_key );
        self::memzero( $mac_key );

        if ( false === $expected_mac || ! hash_equals( $expected_mac, $actual_mac ) ) {
            return new \WP_Error( 'quarantine_auth_failed', 'SecurWP could not verify the integrity of the quarantine file.' );
        }

        return [
            'provider' => 'openssl',
            'sha256'   => hash_final( $sha ),
            'size'     => $size,
        ];
    }

    /**
     * @param resource $source
     * @param resource $dest
     * @return array<string,mixed>|\WP_Error
     */
    private static function encrypt_with_best_provider( $source, $dest ) {
        $providers = self::get_encryption_providers();
        if ( [] === $providers ) {
            return new \WP_Error( 'quarantine_crypto_unavailable', 'No supported encryption provider is available for quarantine storage.' );
        }

        $failures = [];

        foreach ( $providers as $provider ) {
            $reset = self::reset_encryption_streams( $source, $dest );
            if ( is_wp_error( $reset ) ) {
                return $reset;
            }

            try {
                $result = self::encrypt_with_provider( $provider, $source, $dest );
            } catch ( \Throwable $e ) {
                $failures[] = self::format_provider_failure( $provider, $e->getMessage() );
                continue;
            }

            if ( is_wp_error( $result ) ) {
                if ( self::can_fallback_from_provider_error( $result ) && count( $providers ) > count( $failures ) + 1 ) {
                    $failures[] = self::format_provider_failure( $provider, $result->get_error_message() );
                    continue;
                }

                return $result;
            }

            if ( [] !== $failures ) {
                securwp_log( '[SecurWP] Quarantine encryption fallback succeeded with ' . $provider . ' after: ' . implode( '; ', $failures ) );
            }

            return $result;
        }

        if ( [] !== $failures ) {
            securwp_log( '[SecurWP] Quarantine encryption providers failed: ' . implode( '; ', $failures ) );
        }

        return new \WP_Error( 'quarantine_encrypt_failed', 'SecurWP could not encrypt the quarantine file.' );
    }

    private static function derive_key( string $salt, string $context ): string {
        return hash_hkdf(
            'sha256',
            MasterKey::get(),
            32,
            'securwp-quarantine-' . $context,
            $salt
        );
    }

    private static function increment_ctr_iv( string $iv, int $blocks ): string {
        if ( $blocks <= 0 ) {
            return $iv;
        }

        $bytes = array_values( unpack( 'C*', $iv ) );
        $index = count( $bytes ) - 1;
        $carry = $blocks;

        while ( $index >= 0 && $carry > 0 ) {
            $value = $bytes[ $index ] + ( $carry & 0xff );
            $bytes[ $index ] = $value & 0xff;
            $carry = ( $carry >> 8 ) + ( $value >> 8 );
            $index--;
        }

        return pack( 'C*', ...$bytes );
    }

    /**
     * @param resource $handle
     * @return string|false
     */
    private static function read_exact( $handle, int $length ) {
        if ( $length <= 0 ) {
            return '';
        }

        $buffer = '';
        while ( strlen( $buffer ) < $length ) {
            $chunk = fread( $handle, $length - strlen( $buffer ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread
            if ( false === $chunk ) {
                return false;
            }
            if ( '' === $chunk ) {
                break;
            }
            $buffer .= $chunk;
        }

        return strlen( $buffer ) === $length ? $buffer : false;
    }

    /**
     * @param resource $handle
     */
    private static function write_all( $handle, string $data ): bool {
        $written = 0;
        $length = strlen( $data );

        while ( $written < $length ) {
            $result = fwrite( $handle, substr( $data, $written ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite
            if ( false === $result || 0 === $result ) {
                return false;
            }
            $written += $result;
        }

        return true;
    }

    private static function memzero( string &$value ): void {
        if ( extension_loaded( 'sodium' ) && function_exists( 'sodium_memzero' ) ) {
            sodium_memzero( $value );
            return;
        }

        $value = str_repeat( "\0", strlen( $value ) );
    }

    private static function supports_native_sodium_secretstream(): bool {
        return extension_loaded( 'sodium' ) && self::supports_any_sodium_secretstream();
    }

    private static function supports_any_sodium_secretstream(): bool {
        return function_exists( 'sodium_crypto_secretstream_xchacha20poly1305_init_push' )
            && function_exists( 'sodium_crypto_secretstream_xchacha20poly1305_push' )
            && function_exists( 'sodium_crypto_secretstream_xchacha20poly1305_init_pull' )
            && function_exists( 'sodium_crypto_secretstream_xchacha20poly1305_pull' )
            && defined( 'SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE' )
            && defined( 'SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL' );
    }

    private static function supports_openssl_cipher(): bool {
        if ( ! function_exists( 'openssl_encrypt' ) || ! function_exists( 'openssl_decrypt' ) || ! function_exists( 'openssl_cipher_iv_length' ) ) {
            return false;
        }

        return self::OPENSSL_IV_BYTES === openssl_cipher_iv_length( 'aes-256-ctr' );
    }

    /**
     * @return list<string>
     */
    private static function get_encryption_providers(): array {
        $providers = [];

        if ( self::supports_native_sodium_secretstream() ) {
            $providers[] = self::PROVIDER_SODIUM;
        }

        if ( self::supports_openssl_cipher() ) {
            $providers[] = self::PROVIDER_OPENSSL;
        }

        return $providers;
    }

    /**
     * @param resource $source
     * @param resource $dest
     * @return array<string,mixed>|\WP_Error
     */
    private static function encrypt_with_provider( string $provider, $source, $dest ) {
        if ( $provider === self::PROVIDER_OPENSSL ) {
            return self::encrypt_with_openssl( $source, $dest );
        }

        if ( $provider === self::PROVIDER_SODIUM ) {
            return self::encrypt_with_sodium( $source, $dest );
        }

        return new \WP_Error( 'quarantine_crypto_unavailable', 'No supported encryption provider is available for quarantine storage.' );
    }

    /**
     * @param resource $source
     * @param resource $dest
     * @return true|\WP_Error
     */
    private static function reset_encryption_streams( $source, $dest ) {
        if ( 0 !== fseek( $source, 0 ) ) {
            return new \WP_Error( 'quarantine_source_rewind_failed', 'SecurWP could not restart reading the file for quarantine.' );
        }

        if ( 0 !== fseek( $dest, 0 ) ) {
            return new \WP_Error( 'quarantine_destination_reset_failed', 'SecurWP could not prepare the encrypted quarantine file.' );
        }

        if ( ! ftruncate( $dest, 0 ) ) {
            return new \WP_Error( 'quarantine_destination_reset_failed', 'SecurWP could not reset the encrypted quarantine file.' );
        }

        if ( 0 !== fseek( $dest, 0 ) ) {
            return new \WP_Error( 'quarantine_destination_reset_failed', 'SecurWP could not prepare the encrypted quarantine file.' );
        }

        if ( ! self::write_all( $dest, GuardedDataFile::header() ) ) {
            return new \WP_Error( 'quarantine_destination_reset_failed', 'SecurWP could not prepare the encrypted quarantine file.' );
        }

        return true;
    }

    /**
     * @param resource $handle
     */
    private static function seek_to_payload_start( $handle, string $path ): bool {
        $offset = GuardedDataFile::payload_offset_for_path( $path );
        if ( $offset <= 0 ) {
            return true;
        }

        return 0 === fseek( $handle, $offset );
    }

    private static function can_fallback_from_provider_error( \WP_Error $error ): bool {
        return in_array(
            $error->get_error_code(),
            [
                'quarantine_crypto_unavailable',
                'quarantine_sodium_missing',
                'quarantine_sodium_init_failed',
                'quarantine_encrypt_failed',
            ],
            true
        );
    }

    private static function format_provider_failure( string $provider, string $message ): string {
        return $provider . ': ' . sanitize_text_field( $message );
    }
}
