<?php

declare(strict_types=1);

namespace App\Framework\Database\Serialization;

use App\Framework\Database\Metadata\MetadataRegistry;
use App\Framework\Database\TypeConverter;
use ReflectionClass;

/**
 * Serializer for database entities with support for complex types
 */
final class EntitySerializer
{
    public function __construct(
        private readonly MetadataRegistry $metadataRegistry,
        private readonly TypeConverter $typeConverter
    ) {
    }

    /**
     * Serialize entity to array format suitable for caching
     */
    public function serialize(object $entity): array
    {
        $entityClass = $entity::class;
        $metadata = $this->metadataRegistry->getMetadata($entityClass);
        $reflection = new ReflectionClass($entityClass);

        $data = [
            '__class' => $entityClass,
            '__properties' => [],
        ];

        foreach ($metadata->getProperties() as $propertyMetadata) {
            $property = $reflection->getProperty($propertyMetadata->name);

            $value = $property->getValue($entity);

            // Convert to database format for consistent serialization
            $convertedValue = $this->typeConverter->convertToDatabaseValue(
                $value,
                $propertyMetadata->type
            );

            $data['__properties'][$propertyMetadata->name] = [
                'value' => $convertedValue,
                'type' => $propertyMetadata->type,
                'nullable' => $propertyMetadata->nullable,
            ];
        }

        return $data;
    }

    /**
     * Deserialize array data back to entity object
     */
    public function deserialize(array $data, string $expectedClass): object
    {
        if (! isset($data['__class']) || $data['__class'] !== $expectedClass) {
            throw new SerializationException("Serialized data does not match expected class {$expectedClass}");
        }

        if (! isset($data['__properties'])) {
            throw new SerializationException("Invalid serialized entity data - missing properties");
        }

        $reflection = new ReflectionClass($expectedClass);
        $entity = $reflection->newInstanceWithoutConstructor();

        foreach ($data['__properties'] as $propertyName => $propertyData) {
            if (! $reflection->hasProperty($propertyName)) {
                continue; // Skip properties that no longer exist
            }

            $property = $reflection->getProperty($propertyName);

            // Convert from database format back to PHP value
            $value = $this->typeConverter->convertToPHPValue(
                $propertyData['value'],
                $propertyData['type']
            );

            $property->setValue($entity, $value);
        }

        return $entity;
    }

    /**
     * Serialize array of entities
     */
    public function serializeCollection(array $entities): array
    {
        return array_map([$this, 'serialize'], $entities);
    }

    /**
     * Deserialize array of entities
     */
    public function deserializeCollection(array $serializedEntities, string $entityClass): array
    {
        return array_map(
            fn (array $data) => $this->deserialize($data, $entityClass),
            $serializedEntities
        );
    }

    /**
     * Get cache-friendly hash of entity for versioning
     */
    public function getEntityHash(object $entity): string
    {
        $serialized = $this->serialize($entity);

        // Remove class info for hash calculation
        unset($serialized['__class']);

        return md5(serialize($serialized));
    }

    /**
     * Check if serialized data is compatible with current class structure
     */
    public function isCompatible(array $data, string $entityClass): bool
    {
        if (! isset($data['__class']) || $data['__class'] !== $entityClass) {
            return false;
        }

        if (! isset($data['__properties'])) {
            return false;
        }

        $metadata = $this->metadataRegistry->getMetadata($entityClass);
        $reflection = new ReflectionClass($entityClass);

        // Check if all required properties exist
        foreach ($metadata->getProperties() as $propertyMetadata) {
            if (! $propertyMetadata->nullable && ! isset($data['__properties'][$propertyMetadata->name])) {
                return false;
            }

            if (! $reflection->hasProperty($propertyMetadata->name)) {
                return false;
            }
        }

        return true;
    }
}
