<?php

declare(strict_types=1);

namespace App\Framework\EventSourcing\Replay;

use App\Framework\EventSourcing\AggregateEnvelope;
use App\Framework\EventSourcing\AggregateId;
use App\Framework\EventSourcing\EventStore;

/**
 * Event History Visualizer
 *
 * Provides visualization and analysis of event streams
 */
final readonly class EventHistoryVisualizer
{
    public function __construct(
        private EventStore $eventStore
    ) {
    }

    /**
     * Generate timeline visualization
     *
     * @return array{timeline: array, statistics: array}
     */
    public function generateTimeline(AggregateId $aggregateId): array
    {
        $events = $this->eventStore->loadStream($aggregateId);

        if (empty($events)) {
            return ['timeline' => [], 'statistics' => $this->emptyStatistics()];
        }

        $timeline = array_map(
            fn (AggregateEnvelope $envelope) => [
                'version' => $envelope->version,
                'timestamp' => $envelope->timestamp->format('Y-m-d H:i:s'),
                'event_type' => class_basename(get_class($envelope->event)),
                'event_class' => get_class($envelope->event),
                'metadata' => $envelope->metadata,
            ],
            $events
        );

        $statistics = $this->calculateStatistics($events);

        return [
            'timeline' => $timeline,
            'statistics' => $statistics,
        ];
    }

    /**
     * Get event type distribution
     *
     * @return array<string, int>
     */
    public function getEventTypeDistribution(AggregateId $aggregateId): array
    {
        $events = $this->eventStore->loadStream($aggregateId);

        $distribution = [];

        foreach ($events as $envelope) {
            $eventType = class_basename(get_class($envelope->event));

            if (! isset($distribution[$eventType])) {
                $distribution[$eventType] = 0;
            }

            $distribution[$eventType]++;
        }

        arsort($distribution);

        return $distribution;
    }

    /**
     * Get event frequency over time
     *
     * @return array<string, int> Date => count
     */
    public function getEventFrequencyByDay(AggregateId $aggregateId): array
    {
        $events = $this->eventStore->loadStream($aggregateId);

        $frequency = [];

        foreach ($events as $envelope) {
            $date = $envelope->timestamp->format('Y-m-d');

            if (! isset($frequency[$date])) {
                $frequency[$date] = 0;
            }

            $frequency[$date]++;
        }

        ksort($frequency);

        return $frequency;
    }

    /**
     * Get event frequency by hour of day
     *
     * @return array<int, int> Hour (0-23) => count
     */
    public function getEventFrequencyByHour(AggregateId $aggregateId): array
    {
        $events = $this->eventStore->loadStream($aggregateId);

        $frequency = array_fill(0, 24, 0);

        foreach ($events as $envelope) {
            $hour = (int) $envelope->timestamp->format('G');
            $frequency[$hour]++;
        }

        return $frequency;
    }

    /**
     * Find events matching pattern
     */
    public function findEventsMatchingPattern(
        AggregateId $aggregateId,
        callable $matcher
    ): array {
        $events = $this->eventStore->loadStream($aggregateId);

        return array_values(array_filter(
            $events,
            fn (AggregateEnvelope $envelope) => $matcher($envelope)
        ));
    }

    /**
     * Get event stream summary
     */
    public function getStreamSummary(AggregateId $aggregateId): array
    {
        $events = $this->eventStore->loadStream($aggregateId);

        if (empty($events)) {
            return $this->emptyStatistics();
        }

        $first = reset($events);
        $last = end($events);

        return [
            'aggregate_id' => $aggregateId->toString(),
            'total_events' => count($events),
            'first_event' => [
                'version' => $first->version,
                'timestamp' => $first->timestamp->format('Y-m-d H:i:s'),
                'type' => class_basename(get_class($first->event)),
            ],
            'last_event' => [
                'version' => $last->version,
                'timestamp' => $last->timestamp->format('Y-m-d H:i:s'),
                'type' => class_basename(get_class($last->event)),
            ],
            'time_span' => [
                'start' => $first->timestamp->format('Y-m-d H:i:s'),
                'end' => $last->timestamp->format('Y-m-d H:i:s'),
                'duration_hours' => $first->timestamp->diffInHours($last->timestamp),
            ],
            'event_types' => $this->getEventTypeDistribution($aggregateId),
            'average_events_per_day' => $this->calculateAverageEventsPerDay($events),
        ];
    }

    /**
     * Compare two time periods
     */
    public function compareTimePeriods(
        AggregateId $aggregateId,
        array $period1,
        array $period2
    ): array {
        $events = $this->eventStore->loadStream($aggregateId);

        $period1Events = array_filter(
            $events,
            fn ($envelope) => $envelope->timestamp->isBetween($period1['start'], $period1['end'])
        );

        $period2Events = array_filter(
            $events,
            fn ($envelope) => $envelope->timestamp->isBetween($period2['start'], $period2['end'])
        );

        return [
            'period1' => [
                'count' => count($period1Events),
                'types' => $this->getEventTypeDistributionFromEvents($period1Events),
            ],
            'period2' => [
                'count' => count($period2Events),
                'types' => $this->getEventTypeDistributionFromEvents($period2Events),
            ],
            'comparison' => [
                'count_change' => count($period2Events) - count($period1Events),
                'count_change_percent' => count($period1Events) > 0
                    ? round((count($period2Events) - count($period1Events)) / count($period1Events) * 100, 2)
                    : 0,
            ],
        ];
    }

    private function calculateStatistics(array $events): array
    {
        if (empty($events)) {
            return $this->emptyStatistics();
        }

        $first = reset($events);
        $last = end($events);

        return [
            'total_events' => count($events),
            'first_timestamp' => $first->timestamp->format('Y-m-d H:i:s'),
            'last_timestamp' => $last->timestamp->format('Y-m-d H:i:s'),
            'time_span_hours' => $first->timestamp->diffInHours($last->timestamp),
            'average_events_per_day' => $this->calculateAverageEventsPerDay($events),
            'unique_event_types' => count(array_unique(
                array_map(fn ($e) => get_class($e->event), $events)
            )),
        ];
    }

    private function calculateAverageEventsPerDay(array $events): float
    {
        if (empty($events)) {
            return 0.0;
        }

        $first = reset($events);
        $last = end($events);

        $days = max(1, $first->timestamp->diffInDays($last->timestamp));

        return round(count($events) / $days, 2);
    }

    private function emptyStatistics(): array
    {
        return [
            'total_events' => 0,
            'first_timestamp' => null,
            'last_timestamp' => null,
            'time_span_hours' => 0,
            'average_events_per_day' => 0.0,
            'unique_event_types' => 0,
        ];
    }

    private function getEventTypeDistributionFromEvents(array $events): array
    {
        $distribution = [];

        foreach ($events as $envelope) {
            $eventType = class_basename(get_class($envelope->event));

            if (! isset($distribution[$eventType])) {
                $distribution[$eventType] = 0;
            }

            $distribution[$eventType]++;
        }

        arsort($distribution);

        return $distribution;
    }
}
