<?php

declare(strict_types=1);

namespace App\Framework\Analytics\Storage;

use App\Framework\Filesystem\AtomicStorage;
use App\Framework\Filesystem\Exceptions\FilePermissionException;
use App\Framework\Filesystem\Serializer;
use App\Framework\Filesystem\Storage;
use App\Framework\Performance\Contracts\PerformanceCollectorInterface;

/**
 * Analytics-Storage basierend auf dem Performance-System
 *
 * Nutzt das Performance-Framework für effiziente Datenspeicherung
 * und -aggregation von Analytics-Daten.
 */
final class PerformanceBasedAnalyticsStorage implements AnalyticsStorage
{
    /** @var array<string, array> */
    private array $aggregatedData = [];

    /** @var array<array> */
    private array $rawDataBuffer = [];

    private int $bufferSize = 1000;

    public function __construct(
        private PerformanceCollectorInterface $performanceCollector,
        private Storage|AtomicStorage $storage,
        private Serializer $serializer,
        private string $dataPath = '/tmp/analytics'
    ) {
        if (! $this->storage->exists($this->dataPath)) {
            $this->storage->createDirectory($this->dataPath);
        }
    }

    public function storeAggregated(string $period, array $data): void
    {
        error_log("Analytics: storeAggregated called with period={$period}, data=" . json_encode($data));

        $periodKey = $this->getPeriodKey($period);

        if (! isset($this->aggregatedData[$periodKey])) {
            $this->aggregatedData[$periodKey] = [];
        }

        // Merge new data with existing data
        foreach ($data as $key => $value) {
            if (isset($this->aggregatedData[$periodKey][$key])) {
                if (is_numeric($value)) {
                    $this->aggregatedData[$periodKey][$key] += $value;
                } else {
                    $this->aggregatedData[$periodKey][$key] = $value;
                }
            } else {
                $this->aggregatedData[$periodKey][$key] = $value;
            }
        }

        // Persist to file every 10 updates (for better responsiveness in development)
        // Also persist immediately for small amounts to ensure data is not lost
        if (count($this->aggregatedData) % 10 === 0 || count($this->aggregatedData) <= 5) {
            $this->persistAggregatedData();
        }
    }

    public function storeRawData(array $data, float $samplingRate = 0.1): void
    {
        error_log("Analytics: storeRawData called with data=" . json_encode($data) . ", samplingRate={$samplingRate}");

        // Apply sampling
        if ($samplingRate < 1.0 && (random_int(1, 100) / 100) > $samplingRate) {
            error_log("Analytics: Data filtered out by sampling");

            return;
        }

        $this->rawDataBuffer[] = array_merge($data, [
            'timestamp' => time(),
            'sampled' => $samplingRate < 1.0,
        ]);

        // Flush buffer when full
        if (count($this->rawDataBuffer) >= $this->bufferSize) {
            $this->flushRawDataBuffer();
        }
    }

    public function getAggregated(string $startDate, string $endDate, string $period = 'hour'): array
    {
        $this->loadAggregatedData();

        $result = [];
        $start = strtotime($startDate . ' 00:00:00');
        $end = strtotime($endDate . ' 23:59:59');

        foreach ($this->aggregatedData as $periodKey => $data) {
            $timestamp = $this->parseTimestampFromPeriodKey($periodKey, $period);

            if ($timestamp >= $start && $timestamp <= $end) {
                $result[$periodKey] = $data;
            }
        }

        return $result;
    }

    public function getTopList(string $metric, string $startDate, string $endDate, int $limit = 10): array
    {
        $aggregated = $this->getAggregated($startDate, $endDate);
        $topList = [];

        foreach ($aggregated as $period => $data) {
            if (isset($data[$metric])) {
                $value = $data[$metric];

                if (is_array($value)) {
                    foreach ($value as $key => $count) {
                        if (! isset($topList[$key])) {
                            $topList[$key] = 0;
                        }
                        $topList[$key] += $count;
                    }
                }
            }
        }

        arsort($topList);

        return array_slice($topList, 0, $limit, true);
    }

    public function getTimeSeries(string $metric, string $startDate, string $endDate, string $interval = 'hour'): array
    {
        $aggregated = $this->getAggregated($startDate, $endDate, $interval);
        $timeSeries = [];

        foreach ($aggregated as $period => $data) {
            $timestamp = $this->parseTimestampFromPeriodKey($period, $interval);
            $value = $data[$metric] ?? 0;

            if (is_array($value)) {
                $value = array_sum($value);
            }

            $timeSeries[] = [
                'timestamp' => $timestamp,
                'value' => $value,
                'period' => $period,
            ];
        }

        // Sort by timestamp
        usort($timeSeries, fn ($a, $b) => $a['timestamp'] <=> $b['timestamp']);

        return $timeSeries;
    }

    public function cleanup(int $retentionDays): int
    {
        $cutoffTime = time() - ($retentionDays * 24 * 3600);
        $removed = 0;

        // Clean aggregated data
        foreach ($this->aggregatedData as $periodKey => $data) {
            $timestamp = $this->parseTimestampFromPeriodKey($periodKey, 'hour');

            if ($timestamp < $cutoffTime) {
                unset($this->aggregatedData[$periodKey]);
                $removed++;
            }
        }

        // Clean raw data files
        $rawDataFiles = $this->storage->listDirectory($this->dataPath);
        foreach ($rawDataFiles as $file) {
            if (str_starts_with(basename($file), 'raw_') &&
                str_ends_with($file, '.json') &&
                $this->storage->lastModified($file) < $cutoffTime) {
                $this->storage->delete($file);
                $removed++;
            }
        }

        $this->persistAggregatedData();

        return $removed;
    }

    /**
     * Generiert einen Period-Key für Aggregation
     */
    private function getPeriodKey(string $period): string
    {
        $timestamp = time();

        return match ($period) {
            'minute' => date('Y-m-d_H:i', $timestamp),
            'hour' => date('Y-m-d_H', $timestamp),
            'day' => date('Y-m-d', $timestamp),
            'week' => date('Y-W', $timestamp),
            'month' => date('Y-m', $timestamp),
            default => date('Y-m-d_H', $timestamp)
        };
    }

    /**
     * Parst Timestamp aus Period-Key
     */
    private function parseTimestampFromPeriodKey(string $periodKey, string $period): int
    {
        $timestamp = match ($period) {
            'minute' => strtotime($periodKey . ':00'),
            'hour' => strtotime($periodKey . ':00:00'),
            'day' => strtotime($periodKey . ' 00:00:00'),
            'week' => strtotime($periodKey . '-1'), // Monday of week
            'month' => strtotime($periodKey . '-01 00:00:00'),
            default => strtotime($periodKey . ':00:00')
        };

        // Handle invalid date formats
        if ($timestamp === false) {
            // Return current timestamp as fallback
            return time();
        }

        return $timestamp;
    }

    /**
     * Persistiert aggregierte Daten
     */
    private function persistAggregatedData(): void
    {
        error_log("Analytics: persistAggregatedData called - persisting " . count($this->aggregatedData) . " items");
        $filename = $this->dataPath . '/aggregated_' . date('Y-m-d') . '.' . $this->serializer->getFileExtension();
        $content = $this->serializer->serialize($this->aggregatedData);

        try {
            if ($this->storage instanceof AtomicStorage) {
                $this->storage->putAtomic($filename, $content);
            } else {
                $this->storage->put($filename, $content);
            }
        } catch (FilePermissionException $e) {
            error_log("Analytics: Failed to persist aggregated data due to permissions: " . $e->getMessage());
            // Continue gracefully - data will remain in memory for this request
        } catch (\Exception $e) {
            error_log("Analytics: Failed to persist aggregated data: " . $e->getMessage());
        }
    }

    /**
     * Lädt aggregierte Daten
     */
    private function loadAggregatedData(): void
    {
        if (! empty($this->aggregatedData)) {
            return;
        }

        $files = $this->storage->listDirectory($this->dataPath);
        foreach ($files as $file) {
            if (str_starts_with(basename($file), 'aggregated_') &&
                str_ends_with($file, '.' . $this->serializer->getFileExtension()) &&
                $this->storage->exists($file)) {
                $content = $this->storage->get($file);
                $data = $this->serializer->deserialize($content);
                if (is_array($data)) {
                    $this->aggregatedData = array_merge($this->aggregatedData, $data);
                }
            }
        }
    }

    /**
     * Leert den Raw-Data Buffer
     */
    private function flushRawDataBuffer(): void
    {
        if (empty($this->rawDataBuffer)) {
            return;
        }

        $generator = new \App\Framework\Ulid\UlidGenerator();
        $filename = $this->dataPath . '/raw_' . date('Y-m-d_H-i-s') . '_' . $generator->generate() . '.' . $this->serializer->getFileExtension();
        $content = $this->serializer->serialize($this->rawDataBuffer);

        try {
            if ($this->storage instanceof AtomicStorage) {
                $this->storage->putAtomic($filename, $content);
            } else {
                $this->storage->put($filename, $content);
            }
            $this->rawDataBuffer = [];
        } catch (FilePermissionException $e) {
            error_log("Analytics: Failed to flush raw data buffer due to permissions: " . $e->getMessage());
            // Keep data in buffer for potential retry later
        } catch (\Exception $e) {
            error_log("Analytics: Failed to flush raw data buffer: " . $e->getMessage());
            // Clear buffer to prevent memory buildup
            $this->rawDataBuffer = [];
        }
    }

    /**
     * Destructor um Buffer zu leeren
     */
    public function __destruct()
    {
        $this->flushRawDataBuffer();
        $this->persistAggregatedData();
    }
}
