<?php

declare(strict_types=1);

namespace App\Framework\Cache\Warming;

use App\Framework\Cache\Warming\ValueObjects\WarmupMetrics;
use App\Framework\Cache\Warming\ValueObjects\WarmupResult;
use App\Framework\Logging\Logger;
use App\Framework\Logging\ValueObjects\LogContext;

/**
 * Core service for managing cache warming operations
 *
 * Coordinates multiple warming strategies and tracks metrics.
 */
final readonly class CacheWarmingService
{
    /** @var array<WarmupStrategy> */
    private array $strategies;

    public function __construct(
        private Logger $logger,
        array $strategies = []
    ) {
        // Sort strategies by priority (highest first)
        $this->strategies = $this->sortStrategiesByPriority($strategies);
    }

    /**
     * Execute all registered warming strategies
     *
     * @param bool $force Force execution even if shouldRun() returns false
     * @return WarmupMetrics Aggregated metrics from all strategies
     */
    public function warmAll(bool $force = false): WarmupMetrics
    {
        $this->logger->info(
            'Starting cache warmup',
            LogContext::withData([
                'strategies_count' => count($this->strategies),
                'force' => $force
            ])
        );

        $results = [];

        foreach ($this->strategies as $strategy) {
            if (!$force && !$strategy->shouldRun()) {
                $this->logger->debug(
                    'Skipping strategy (shouldRun returned false)',
                    LogContext::withData(['strategy' => $strategy->getName()])
                );
                continue;
            }

            $result = $this->executeStrategy($strategy);
            $results[] = $result;
        }

        $metrics = WarmupMetrics::fromResults($results);

        $this->logger->info(
            'Cache warmup completed',
            LogContext::withData($metrics->toArray())
        );

        return $metrics;
    }

    /**
     * Execute a specific warming strategy by name
     *
     * @param string $strategyName Name of the strategy to execute
     * @return WarmupResult Result of the strategy execution
     * @throws \InvalidArgumentException If strategy not found
     */
    public function warmStrategy(string $strategyName): WarmupResult
    {
        $strategy = $this->findStrategy($strategyName);

        if ($strategy === null) {
            throw new \InvalidArgumentException(
                "Strategy '{$strategyName}' not found. Available: " .
                implode(', ', $this->getStrategyNames())
            );
        }

        return $this->executeStrategy($strategy);
    }

    /**
     * Execute only strategies with a specific minimum priority
     *
     * @param int $minPriority Minimum priority level
     * @return WarmupMetrics Aggregated metrics
     */
    public function warmByPriority(int $minPriority): WarmupMetrics
    {
        $this->logger->info(
            'Starting priority-based cache warmup',
            LogContext::withData(['min_priority' => $minPriority])
        );

        $results = [];

        foreach ($this->strategies as $strategy) {
            if ($strategy->getPriority() >= $minPriority) {
                $result = $this->executeStrategy($strategy);
                $results[] = $result;
            }
        }

        return WarmupMetrics::fromResults($results);
    }

    /**
     * Get list of all registered strategies
     *
     * @return array<array{name: string, priority: int, estimated_duration: int}>
     */
    public function getStrategies(): array
    {
        return array_map(function (WarmupStrategy $strategy) {
            return [
                'name' => $strategy->getName(),
                'priority' => $strategy->getPriority(),
                'estimated_duration' => $strategy->getEstimatedDuration(),
                'should_run' => $strategy->shouldRun()
            ];
        }, $this->strategies);
    }

    /**
     * Get estimated total warmup duration in seconds
     */
    public function getEstimatedTotalDuration(): int
    {
        $total = 0;
        foreach ($this->strategies as $strategy) {
            if ($strategy->shouldRun()) {
                $total += $strategy->getEstimatedDuration();
            }
        }
        return $total;
    }

    private function executeStrategy(WarmupStrategy $strategy): WarmupResult
    {
        $this->logger->info(
            'Executing warmup strategy',
            LogContext::withData([
                'strategy' => $strategy->getName(),
                'priority' => $strategy->getPriority(),
                'estimated_duration' => $strategy->getEstimatedDuration()
            ])
        );

        try {
            $result = $strategy->warmup();

            $this->logger->info(
                'Strategy completed',
                LogContext::withData(array_merge(
                    ['strategy' => $strategy->getName()],
                    $result->toArray()
                ))
            );

            return $result;
        } catch (\Throwable $e) {
            $this->logger->error(
                'Strategy failed',
                LogContext::withData([
                    'strategy' => $strategy->getName(),
                    'error' => $e->getMessage(),
                    'trace' => $e->getTraceAsString()
                ])
            );

            // Return failed result
            return new WarmupResult(
                strategyName: $strategy->getName(),
                itemsWarmed: 0,
                itemsFailed: 1,
                durationSeconds: 0.0,
                memoryUsedBytes: 0,
                errors: [['error' => $e->getMessage()]]
            );
        }
    }

    private function findStrategy(string $name): ?WarmupStrategy
    {
        foreach ($this->strategies as $strategy) {
            if ($strategy->getName() === $name) {
                return $strategy;
            }
        }
        return null;
    }

    private function getStrategyNames(): array
    {
        return array_map(fn($s) => $s->getName(), $this->strategies);
    }

    /**
     * @param array<WarmupStrategy> $strategies
     * @return array<WarmupStrategy>
     */
    private function sortStrategiesByPriority(array $strategies): array
    {
        usort($strategies, fn($a, $b) => $b->getPriority() <=> $a->getPriority());
        return $strategies;
    }
}
