<?php

declare(strict_types=1);

namespace App\Framework\Deployment\Pipeline\Services;

use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheItem;
use App\Framework\Cache\CacheKey;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Deployment\Pipeline\ValueObjects\PipelineId;
use App\Framework\Deployment\Pipeline\ValueObjects\PipelineStatus;
use App\Framework\Deployment\Pipeline\ValueObjects\PipelineStage;
use App\Framework\Deployment\Pipeline\ValueObjects\DeploymentEnvironment;

/**
 * Cache-based storage for active pipeline execution state
 *
 * Stores current status, stage progress, and execution data
 * for ongoing pipeline runs to enable real-time monitoring.
 */
final readonly class PipelineStatusStore
{
    public function __construct(
        private Cache $cache
    ) {}

    /**
     * Store initial pipeline state
     */
    public function initializePipeline(
        PipelineId $pipelineId,
        DeploymentEnvironment $environment,
        array $stages
    ): void {
        $data = [
            'pipeline_id' => $pipelineId->value,
            'environment' => $environment->value,
            'status' => PipelineStatus::PENDING->value,
            'stages' => array_map(fn($stage) => [
                'stage' => $stage->value,
                'status' => 'pending',
                'started_at' => null,
                'completed_at' => null,
                'duration_ms' => null,
                'error' => null
            ], $stages),
            'started_at' => time(),
            'completed_at' => null,
            'total_duration_ms' => null
        ];

        $this->storeState($pipelineId, $data);
    }

    /**
     * Update pipeline status
     */
    public function updatePipelineStatus(
        PipelineId $pipelineId,
        PipelineStatus $status
    ): void {
        $state = $this->getState($pipelineId);

        if ($state === null) {
            return;
        }

        $state['status'] = $status->value;

        if ($status->isTerminal()) {
            $state['completed_at'] = time();
            $state['total_duration_ms'] = ($state['completed_at'] - $state['started_at']) * 1000;
        }

        $this->storeState($pipelineId, $state);
    }

    /**
     * Update stage status
     */
    public function updateStageStatus(
        PipelineId $pipelineId,
        PipelineStage $stage,
        string $status,
        ?int $durationMs = null,
        ?string $error = null
    ): void {
        $state = $this->getState($pipelineId);

        if ($state === null) {
            return;
        }

        foreach ($state['stages'] as &$stageData) {
            if ($stageData['stage'] === $stage->value) {
                $stageData['status'] = $status;

                if ($status === 'running') {
                    $stageData['started_at'] = time();
                } elseif (in_array($status, ['success', 'failed'], true)) {
                    $stageData['completed_at'] = time();
                    $stageData['duration_ms'] = $durationMs;
                    $stageData['error'] = $error;
                }

                break;
            }
        }

        $this->storeState($pipelineId, $state);
    }

    /**
     * Get current pipeline state
     *
     * @return array|null Pipeline state or null if not found
     */
    public function getState(PipelineId $pipelineId): ?array
    {
        $cacheKey = $this->getCacheKey($pipelineId);

        $cacheItem = $this->cache->get($cacheKey);

        return $cacheItem?->value;
    }

    /**
     * Check if pipeline exists
     */
    public function exists(PipelineId $pipelineId): bool
    {
        return $this->getState($pipelineId) !== null;
    }

    /**
     * Remove pipeline state (after completion)
     */
    public function remove(PipelineId $pipelineId): void
    {
        $cacheKey = $this->getCacheKey($pipelineId);
        $this->cache->forget($cacheKey);
    }

    /**
     * Store state in cache
     */
    private function storeState(PipelineId $pipelineId, array $data): void
    {
        $cacheKey = $this->getCacheKey($pipelineId);

        $cacheItem = CacheItem::forSetting(
            key: $cacheKey,
            value: $data,
            ttl: Duration::fromHours(1) // Keep for 1 hour
        );

        $this->cache->set($cacheItem);
    }

    /**
     * Generate cache key for pipeline
     */
    private function getCacheKey(PipelineId $pipelineId): CacheKey
    {
        return CacheKey::fromString("pipeline_status_{$pipelineId->value}");
    }
}
