<?php

declare(strict_types=1);

namespace App\Framework\EventSourcing\Snapshots;

use App\Framework\EventSourcing\AggregateId;
use App\Framework\EventSourcing\AggregateRoot;

/**
 * Snapshotable Aggregate Helper
 *
 * Extends AggregateRoot functionality with snapshot support
 * without modifying the core AggregateRoot class
 */
final readonly class SnapshotableAggregate
{
    public function __construct(
        private SnapshotStore $store,
        private SnapshotStrategy $strategy
    ) {
    }

    /**
     * Load aggregate with snapshot optimization
     */
    public function load(
        AggregateId $aggregateId,
        callable $eventStreamLoader,
        callable $aggregateFactory
    ): AggregateRoot {
        // Try to load latest snapshot
        $snapshot = $this->store->getLatest($aggregateId);

        if ($snapshot) {
            // Load from snapshot + events since snapshot
            $eventsSinceSnapshot = $eventStreamLoader($snapshot->getVersion());
            $state = $this->reconstructState($snapshot);

            return AggregateRoot::rehydrate(
                $aggregateId,
                $state,
                $eventsSinceSnapshot
            );
        }

        // No snapshot - load all events
        $events = $eventStreamLoader(0);

        return $aggregateFactory($aggregateId, $events);
    }

    /**
     * Save aggregate and create snapshot if strategy says so
     */
    public function saveWithSnapshot(AggregateRoot $aggregate): void
    {
        // Check if snapshot should be taken
        if ($this->strategy->shouldTakeSnapshot($aggregate)) {
            $snapshot = $this->createSnapshot($aggregate);
            $this->store->save($snapshot);
        }
    }

    /**
     * Force snapshot creation
     */
    public function forceSnapshot(AggregateRoot $aggregate): Snapshot
    {
        $snapshot = $this->createSnapshot($aggregate);
        $this->store->save($snapshot);

        return $snapshot;
    }

    /**
     * Get aggregate version from event recorder
     */
    public function getVersion(AggregateRoot $aggregate): int
    {
        return $aggregate->eventRecorder->getVersion();
    }

    /**
     * Create snapshot from aggregate
     */
    private function createSnapshot(AggregateRoot $aggregate): Snapshot
    {
        return Snapshot::take(
            aggregateId: $aggregate->id,
            version: $this->getVersion($aggregate),
            state: $this->extractState($aggregate),
            aggregateClass: get_class($aggregate->state)
        );
    }

    /**
     * Extract state array from aggregate
     */
    private function extractState(AggregateRoot $aggregate): array
    {
        // Use reflection to extract state properties
        $reflection = new \ReflectionObject($aggregate->state);
        $state = [];

        foreach ($reflection->getProperties() as $property) {
            $property->setAccessible(true);
            $value = $property->getValue($aggregate->state);

            // Serialize Value Objects and complex types
            if (is_object($value)) {
                if (method_exists($value, 'toArray')) {
                    $state[$property->getName()] = $value->toArray();
                } elseif (method_exists($value, 'toString')) {
                    $state[$property->getName()] = $value->toString();
                } else {
                    $state[$property->getName()] = serialize($value);
                }
            } else {
                $state[$property->getName()] = $value;
            }
        }

        return $state;
    }

    /**
     * Reconstruct state object from snapshot
     */
    private function reconstructState(SnapshotInterface $snapshot): object
    {
        $stateClass = $snapshot->getAggregateClass();
        $stateData = $snapshot->getState();

        // Simple reconstruction - would need enhancement for complex VOs
        return new $stateClass(...$stateData);
    }
}
