<?php

declare(strict_types=1);

namespace App\Framework\MachineLearning\ModelManagement;

use App\Framework\MachineLearning\ModelManagement\ValueObjects\ModelMetadata;
use App\Framework\MachineLearning\ModelManagement\ValueObjects\ModelType;
use App\Framework\MachineLearning\ModelManagement\Exceptions\ModelAlreadyExistsException;
use App\Framework\MachineLearning\ModelManagement\Exceptions\ModelNotFoundException;
use App\Framework\Core\ValueObjects\Version;

/**
 * In-Memory Model Registry - For Testing
 *
 * Stores models in memory, useful for unit tests and development.
 * All data is lost when object is destroyed.
 */
final class InMemoryModelRegistry implements ModelRegistry
{
    /**
     * @var array<string, ModelMetadata> Keyed by "modelName@version"
     */
    private array $models = [];

    public function register(ModelMetadata $metadata): void
    {
        $key = $this->getKey($metadata->modelName, $metadata->version);

        if (isset($this->models[$key])) {
            throw ModelAlreadyExistsException::forModel($metadata->getModelId());
        }

        $this->models[$key] = $metadata;
    }

    public function get(string $modelName, Version $version): ?ModelMetadata
    {
        $key = $this->getKey($modelName, $version);
        return $this->models[$key] ?? null;
    }

    public function getLatest(string $modelName): ?ModelMetadata
    {
        $versions = $this->getAll($modelName);

        if (empty($versions)) {
            return null;
        }

        // Already sorted by version (newest first)
        return $versions[0];
    }

    public function getAll(string $modelName): array
    {
        $models = array_filter(
            $this->models,
            fn(ModelMetadata $m) => $m->modelName === $modelName
        );

        // Sort by version (newest first)
        usort($models, fn(ModelMetadata $a, ModelMetadata $b) =>
            $b->version->isNewerThan($a->version) ? 1 : -1
        );

        return array_values($models);
    }

    public function getByType(ModelType $type): array
    {
        return array_values(array_filter(
            $this->models,
            fn(ModelMetadata $m) => $m->modelType === $type
        ));
    }

    public function getByEnvironment(string $environment): array
    {
        return array_values(array_filter(
            $this->models,
            fn(ModelMetadata $m) => $m->environment === $environment
        ));
    }

    public function getProductionModels(): array
    {
        return $this->getByEnvironment('production');
    }

    public function update(ModelMetadata $metadata): void
    {
        $key = $this->getKey($metadata->modelName, $metadata->version);

        if (!isset($this->models[$key])) {
            throw ModelNotFoundException::forModel($metadata->getModelId());
        }

        $this->models[$key] = $metadata;
    }

    public function delete(string $modelName, Version $version): bool
    {
        $key = $this->getKey($modelName, $version);

        if (!isset($this->models[$key])) {
            return false;
        }

        unset($this->models[$key]);
        return true;
    }

    public function exists(string $modelName, Version $version): bool
    {
        $key = $this->getKey($modelName, $version);
        return isset($this->models[$key]);
    }

    public function getAllModelNames(): array
    {
        $names = array_map(
            fn(ModelMetadata $m) => $m->modelName,
            $this->models
        );

        return array_values(array_unique($names));
    }

    public function getVersionCount(string $modelName): int
    {
        return count($this->getAll($modelName));
    }

    public function getTotalCount(): int
    {
        return count($this->models);
    }

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

    private function getKey(string $modelName, Version $version): string
    {
        return "{$modelName}@{$version->toString()}";
    }
}
