<?php

declare(strict_types=1);

namespace App\Framework\LiveComponents\Serialization;

use App\Framework\LiveComponents\Exceptions\StateEncryptionException;

/**
 * Encrypts serialized state data for LiveComponents
 *
 * Decorator pattern - wraps state serialization with encryption layer.
 * Uses StateEncryptor for authenticated encryption (AES-256-GCM via libsodium).
 *
 * Framework Principles:
 * - Readonly class with composition over inheritance
 * - Decorator pattern for transparent encryption layer
 * - Value Objects for all inputs/outputs
 * - Explicit exceptions for all error conditions
 *
 * Usage:
 * Components can opt-in to encryption via attribute:
 *   #[EncryptedState]
 *   final readonly class PaymentFormComponent implements LiveComponentContract
 *
 * @see StateEncryptor For encryption implementation
 */
final readonly class EncryptedStateSerializer
{
    public function __construct(
        private StateEncryptor $encryptor
    ) {
    }

    /**
     * Serialize and encrypt state data
     *
     * @param object $state The state Value Object (must implement toArray())
     * @return string Base64-encoded encrypted serialized state
     * @throws StateEncryptionException if encryption fails
     */
    public function serialize(object $state): string
    {
        try {
            // 1. Convert state object to array
            if (! method_exists($state, 'toArray')) {
                throw new \InvalidArgumentException(
                    'State object must implement toArray() method. ' .
                    'Got: ' . get_class($state)
                );
            }

            $stateArray = $state->toArray();

            // 2. Serialize to JSON
            $json = json_encode($stateArray, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);

            if ($json === false) {
                throw new StateEncryptionException(
                    'Failed to serialize state to JSON: ' . json_last_error_msg()
                );
            }

            // 3. Encrypt the serialized data
            return $this->encryptor->encrypt($json);
        } catch (StateEncryptionException $e) {
            throw $e; // Re-throw encryption exceptions
        } catch (\JsonException $e) {
            throw StateEncryptionException::encryptionFailed(
                'JSON serialization failed: ' . $e->getMessage(),
                $e
            );
        } catch (\Throwable $e) {
            throw StateEncryptionException::encryptionFailed(
                'Serialization failed: ' . $e->getMessage(),
                $e
            );
        }
    }

    /**
     * Decrypt and deserialize state data
     *
     * @param string $encrypted Base64-encoded encrypted serialized state
     * @param string $className The state class name to deserialize to
     * @return object The deserialized state Value Object
     * @throws StateEncryptionException if decryption or deserialization fails
     */
    public function deserialize(string $encrypted, string $className): object
    {
        try {
            // 1. Decrypt the data
            $json = $this->encryptor->decrypt($encrypted);

            // 2. Deserialize from JSON
            $stateArray = json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR);

            if (! is_array($stateArray)) {
                throw StateEncryptionException::dataCorrupted(
                    'Decrypted data is not a valid array'
                );
            }

            // 3. Create state object from array
            if (! method_exists($className, 'fromArray')) {
                throw new \InvalidArgumentException(
                    "State class {$className} must implement fromArray() method"
                );
            }

            $state = $className::fromArray($stateArray);

            if (! is_object($state)) {
                throw StateEncryptionException::dataCorrupted(
                    "fromArray() did not return an object"
                );
            }

            return $state;
        } catch (StateEncryptionException $e) {
            throw $e; // Re-throw encryption exceptions
        } catch (\JsonException $e) {
            throw StateEncryptionException::decryptionFailed(
                'JSON deserialization failed: ' . $e->getMessage(),
                $e
            );
        } catch (\Throwable $e) {
            throw StateEncryptionException::decryptionFailed(
                'Deserialization failed: ' . $e->getMessage(),
                $e
            );
        }
    }

    /**
     * Check if data is encrypted
     *
     * @param string $data The data to check
     * @return bool True if data appears to be encrypted
     */
    public function isEncrypted(string $data): bool
    {
        return $this->encryptor->isEncrypted($data);
    }
}
