<?php

declare(strict_types=1);

namespace App\Framework\SlidingWindow;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\SlidingWindow\Aggregator\SlidingWindowAggregator;

/**
 * In-memory sliding window implementation
 */
final class InMemorySlidingWindow implements SlidingWindow
{
    /**
     * @var array<WindowEntry>
     */
    private array $entries = [];

    public function __construct(
        private readonly Duration $windowSize,
        private readonly string $identifier,
        private readonly SlidingWindowAggregator $aggregator,
    ) {
    }

    public function record(mixed $value, Timestamp $timestamp): void
    {
        $this->entries[] = new WindowEntry($value, $timestamp);
        $this->cleanup();
    }

    public function getValues(?Timestamp $now = null): array
    {
        $now ??= Timestamp::now();
        $this->cleanup($now);

        return array_map(
            fn (WindowEntry $entry) => $entry->value,
            $this->getValidEntries($now)
        );
    }

    public function getStats(?Timestamp $now = null): SlidingWindowStats
    {
        $now ??= Timestamp::now();
        $this->cleanup($now);

        $validEntries = $this->getValidEntries($now);
        $windowStart = $now->diff($this->windowSize) > Duration::zero()
            ? Timestamp::fromFloat($now->toFloat() - $this->windowSize->toSeconds())
            : $now;

        $aggregatedData = [];
        if (! empty($validEntries)) {
            $result = $this->aggregator->aggregate(...$validEntries);
            $aggregatedData = method_exists($result, 'toArray') ? $result->toArray() : [$this->aggregator->getIdentifier() => $result];
        }

        return new SlidingWindowStats(
            totalCount: count($validEntries),
            windowSize: $this->windowSize,
            windowStart: $windowStart,
            windowEnd: $now,
            aggregatedData: $aggregatedData
        );
    }

    public function clear(): void
    {
        $this->entries = [];
    }

    public function getWindowSize(): Duration
    {
        return $this->windowSize;
    }

    public function getIdentifier(): string
    {
        return $this->identifier;
    }

    /**
     * Get all valid entries within the window
     *
     * @return array<WindowEntry>
     */
    private function getValidEntries(Timestamp $now): array
    {
        $cutoffTime = Timestamp::fromFloat($now->toFloat() - $this->windowSize->toSeconds());

        return array_filter(
            $this->entries,
            fn (WindowEntry $entry) => $entry->isWithinWindow($cutoffTime)
        );
    }

    /**
     * Remove entries outside the window
     */
    private function cleanup(?Timestamp $now = null): void
    {
        $now ??= Timestamp::now();
        $this->entries = $this->getValidEntries($now);
    }
}
