<?php

declare(strict_types=1);

namespace App\Application\Api\MachineLearning;

use App\Framework\Attributes\Route;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Core\ValueObjects\Version;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Http\Status;
use App\Framework\MachineLearning\ModelManagement\ModelPerformanceMonitor;
use App\Framework\MachineLearning\ModelManagement\ModelRegistry;
use App\Framework\MachineLearning\ModelManagement\ValueObjects\ModelMetadata;
use App\Framework\MachineLearning\ModelManagement\ValueObjects\ModelType;
use App\Framework\OpenApi\Attributes\ApiEndpoint;
use App\Framework\OpenApi\Attributes\ApiParameter;
use App\Framework\OpenApi\Attributes\ApiRequestBody;
use App\Framework\OpenApi\Attributes\ApiResponse;
use App\Framework\OpenApi\Attributes\ApiSecurity;
use App\Framework\Router\Result\JsonResult;

/**
 * ML Models Management API Controller
 *
 * RESTful API endpoints for managing machine learning models:
 * - Model registration
 * - Performance metrics retrieval
 * - Model listing and versioning
 */
#[ApiSecurity('bearerAuth')]
final readonly class MLModelsController
{
    public function __construct(
        private ModelRegistry $registry,
        private ModelPerformanceMonitor $performanceMonitor
    ) {}

    #[Route(path: '/api/ml/models', method: Method::GET)]
    #[ApiEndpoint(
        summary: 'List all ML models',
        description: 'Retrieve a list of all registered machine learning models with their versions',
        tags: ['Machine Learning'],
    )]
    #[ApiParameter(
        name: 'type',
        in: 'query',
        description: 'Filter by model type (supervised, unsupervised, reinforcement)',
        required: false,
        type: 'string',
        example: 'supervised',
    )]
    #[ApiResponse(
        statusCode: 200,
        description: 'List of ML models retrieved successfully',
        example: [
            'models' => [
                [
                    'model_name' => 'fraud-detector',
                    'type' => 'supervised',
                    'versions' => [
                        [
                            'version' => '1.0.0',
                            'created_at' => '2024-01-01T00:00:00Z',
                            'is_latest' => true,
                        ],
                    ],
                ],
            ],
            'total_models' => 5,
        ],
    )]
    public function listModels(HttpRequest $request): JsonResult
    {
        $typeFilter = $request->query->get('type');

        // Get all model names
        $modelNames = $this->registry->getAllModelNames();

        // Get all versions for each model
        $allModels = [];
        foreach ($modelNames as $modelName) {
            $versions = $this->registry->getAll($modelName);
            $allModels = array_merge($allModels, $versions);
        }

        // Filter by type if specified
        if ($typeFilter !== null) {
            $allModels = array_filter($allModels, function (ModelMetadata $metadata) use ($typeFilter) {
                return strtolower($metadata->modelType->value) === strtolower($typeFilter);
            });
        }

        // Group by model name
        $groupedModels = [];
        foreach ($allModels as $metadata) {
            $modelName = $metadata->modelName;

            if (!isset($groupedModels[$modelName])) {
                $groupedModels[$modelName] = [
                    'model_name' => $modelName,
                    'type' => $metadata->modelType->value,
                    'versions' => [],
                ];
            }

            $groupedModels[$modelName]['versions'][] = [
                'version' => $metadata->version->toString(),
                'created_at' => $metadata->createdAt->format('Y-m-d\TH:i:s\Z'),
                'configuration' => $metadata->configuration,
            ];
        }

        return new JsonResult([
            'models' => array_values($groupedModels),
            'total_models' => count($groupedModels),
        ]);
    }

    #[Route(path: '/api/ml/models/{modelName}', method: Method::GET)]
    #[ApiEndpoint(
        summary: 'Get model details',
        description: 'Retrieve detailed information about a specific ML model',
        tags: ['Machine Learning'],
    )]
    #[ApiParameter(
        name: 'modelName',
        in: 'path',
        description: 'Model identifier',
        required: true,
        type: 'string',
        example: 'fraud-detector',
    )]
    #[ApiParameter(
        name: 'version',
        in: 'query',
        description: 'Specific version (optional, defaults to latest)',
        required: false,
        type: 'string',
        example: '1.0.0',
    )]
    #[ApiResponse(
        statusCode: 200,
        description: 'Model details retrieved successfully',
        example: [
            'model_name' => 'fraud-detector',
            'type' => 'supervised',
            'version' => '1.0.0',
            'configuration' => [
                'threshold' => 0.7,
                'algorithm' => 'random_forest',
            ],
            'created_at' => '2024-01-01T00:00:00Z',
        ],
    )]
    #[ApiResponse(
        statusCode: 404,
        description: 'Model not found',
    )]
    public function getModel(string $modelName, HttpRequest $request): JsonResult
    {
        $versionString = $request->query->get('version');

        try {
            if ($versionString !== null) {
                $version = Version::fromString($versionString);
                $metadata = $this->registry->get($modelName, $version);
            } else {
                $metadata = $this->registry->getLatest($modelName);
            }

            if ($metadata === null) {
                return new JsonResult([
                    'error' => 'Model not found',
                    'model_name' => $modelName,
                ], Status::NOT_FOUND);
            }

            return new JsonResult([
                'model_name' => $metadata->modelName,
                'type' => $metadata->modelType->value,
                'version' => $metadata->version->toString(),
                'configuration' => $metadata->configuration,
                'performance_metrics' => $metadata->performanceMetrics,
                'created_at' => $metadata->createdAt->format('Y-m-d\TH:i:s\Z'),
            ]);
        } catch (\InvalidArgumentException $e) {
            return new JsonResult([
                'error' => 'Invalid version format',
                'message' => $e->getMessage(),
            ], Status::BAD_REQUEST);
        }
    }

    #[Route(path: '/api/ml/models/{modelName}/metrics', method: Method::GET)]
    #[ApiEndpoint(
        summary: 'Get model performance metrics',
        description: 'Retrieve real-time performance metrics for a specific model',
        tags: ['Machine Learning'],
    )]
    #[ApiParameter(
        name: 'modelName',
        in: 'path',
        description: 'Model identifier',
        required: true,
        type: 'string',
        example: 'fraud-detector',
    )]
    #[ApiParameter(
        name: 'version',
        in: 'query',
        description: 'Model version',
        required: false,
        type: 'string',
        example: '1.0.0',
    )]
    #[ApiParameter(
        name: 'timeWindow',
        in: 'query',
        description: 'Time window in hours (default: 1)',
        required: false,
        type: 'integer',
        example: 24,
    )]
    #[ApiResponse(
        statusCode: 200,
        description: 'Performance metrics retrieved successfully',
        example: [
            'model_name' => 'fraud-detector',
            'version' => '1.0.0',
            'time_window_hours' => 24,
            'metrics' => [
                'accuracy' => 0.92,
                'precision' => 0.89,
                'recall' => 0.94,
                'f1_score' => 0.91,
                'total_predictions' => 1523,
                'average_confidence' => 0.85,
            ],
            'confusion_matrix' => [
                'true_positive' => 1234,
                'true_negative' => 156,
                'false_positive' => 89,
                'false_negative' => 44,
            ],
        ],
    )]
    #[ApiResponse(
        statusCode: 404,
        description: 'Model not found',
    )]
    public function getMetrics(string $modelName, HttpRequest $request): JsonResult
    {
        $versionString = $request->query->get('version');
        $timeWindowHours = $request->query->getInt('timeWindow', 1);

        try {
            if ($versionString !== null) {
                $version = Version::fromString($versionString);
            } else {
                $metadata = $this->registry->getLatest($modelName);
                if ($metadata === null) {
                    return new JsonResult([
                        'error' => 'Model not found',
                        'model_name' => $modelName,
                    ], Status::NOT_FOUND);
                }
                $version = $metadata->version;
            }

            $timeWindow = Duration::fromHours($timeWindowHours);
            $metrics = $this->performanceMonitor->getCurrentMetrics(
                $modelName,
                $version,
                $timeWindow
            );

            return new JsonResult([
                'model_name' => $modelName,
                'version' => $version->toString(),
                'time_window_hours' => $timeWindowHours,
                'metrics' => [
                    'accuracy' => $metrics['accuracy'],
                    'precision' => $metrics['precision'] ?? null,
                    'recall' => $metrics['recall'] ?? null,
                    'f1_score' => $metrics['f1_score'] ?? null,
                    'total_predictions' => $metrics['total_predictions'],
                    'average_confidence' => $metrics['average_confidence'] ?? null,
                ],
                'confusion_matrix' => $metrics['confusion_matrix'] ?? null,
                'timestamp' => date('c'),
            ]);
        } catch (\InvalidArgumentException $e) {
            return new JsonResult([
                'error' => 'Invalid parameters',
                'message' => $e->getMessage(),
            ], Status::BAD_REQUEST);
        }
    }

    #[Route(path: '/api/ml/models', method: Method::POST)]
    #[ApiEndpoint(
        summary: 'Register a new ML model',
        description: 'Register a new machine learning model or version in the system',
        tags: ['Machine Learning'],
    )]
    #[ApiRequestBody(
        description: 'Model metadata for registration',
        required: true,
        example: [
            'model_name' => 'fraud-detector',
            'type' => 'supervised',
            'version' => '2.0.0',
            'configuration' => [
                'threshold' => 0.75,
                'algorithm' => 'xgboost',
                'features' => 30,
            ],
            'performance_metrics' => [
                'accuracy' => 0.94,
                'precision' => 0.91,
                'recall' => 0.96,
            ],
        ],
    )]
    #[ApiResponse(
        statusCode: 201,
        description: 'Model registered successfully',
        example: [
            'model_name' => 'fraud-detector',
            'version' => '2.0.0',
            'created_at' => '2024-01-01T00:00:00Z',
            'message' => 'Model registered successfully',
        ],
    )]
    #[ApiResponse(
        statusCode: 400,
        description: 'Invalid model data',
    )]
    #[ApiResponse(
        statusCode: 409,
        description: 'Model version already exists',
    )]
    public function registerModel(HttpRequest $request): JsonResult
    {
        try {
            $data = $request->parsedBody->toArray();

            // Validate required fields
            if (!isset($data['model_name'], $data['type'], $data['version'])) {
                return new JsonResult([
                    'error' => 'Missing required fields',
                    'required' => ['model_name', 'type', 'version'],
                ], Status::BAD_REQUEST);
            }

            // Parse model type
            $modelType = match (strtolower($data['type'])) {
                'supervised' => ModelType::SUPERVISED,
                'unsupervised' => ModelType::UNSUPERVISED,
                'reinforcement' => ModelType::REINFORCEMENT,
                default => throw new \InvalidArgumentException("Invalid model type: {$data['type']}")
            };

            // Create metadata
            $metadata = new ModelMetadata(
                modelName: $data['model_name'],
                modelType: $modelType,
                version: Version::fromString($data['version']),
                configuration: $data['configuration'] ?? [],
                createdAt: Timestamp::now(),
                performanceMetrics: $data['performance_metrics'] ?? []
            );

            // Check if already exists
            $existing = $this->registry->get($metadata->modelName, $metadata->version);
            if ($existing !== null) {
                return new JsonResult([
                    'error' => 'Model version already exists',
                    'model_name' => $metadata->modelName,
                    'version' => $metadata->version->toString(),
                ], Status::CONFLICT);
            }

            // Register model
            $this->registry->register($metadata);

            return new JsonResult([
                'model_name' => $metadata->modelName,
                'version' => $metadata->version->toString(),
                'type' => $metadata->modelType->value,
                'created_at' => $metadata->createdAt->format('Y-m-d\TH:i:s\Z'),
                'message' => 'Model registered successfully',
            ], Status::CREATED);
        } catch (\InvalidArgumentException $e) {
            return new JsonResult([
                'error' => 'Invalid model data',
                'message' => $e->getMessage(),
            ], Status::BAD_REQUEST);
        } catch (\Throwable $e) {
            return new JsonResult([
                'error' => 'Failed to register model',
                'message' => $e->getMessage(),
            ], Status::INTERNAL_SERVER_ERROR);
        }
    }

    #[Route(path: '/api/ml/models/{modelName}', method: Method::DELETE)]
    #[ApiEndpoint(
        summary: 'Unregister ML model',
        description: 'Remove a specific version of an ML model from the registry',
        tags: ['Machine Learning'],
    )]
    #[ApiParameter(
        name: 'modelName',
        in: 'path',
        description: 'Model identifier',
        required: true,
        type: 'string',
        example: 'fraud-detector',
    )]
    #[ApiParameter(
        name: 'version',
        in: 'query',
        description: 'Model version to unregister',
        required: true,
        type: 'string',
        example: '1.0.0',
    )]
    #[ApiResponse(
        statusCode: 200,
        description: 'Model unregistered successfully',
    )]
    #[ApiResponse(
        statusCode: 404,
        description: 'Model not found',
    )]
    public function unregisterModel(string $modelName, HttpRequest $request): JsonResult
    {
        $versionString = $request->query->get('version');

        if ($versionString === null) {
            return new JsonResult([
                'error' => 'Version parameter is required',
            ], Status::BAD_REQUEST);
        }

        try {
            $version = Version::fromString($versionString);

            // Check if model exists
            $metadata = $this->registry->get($modelName, $version);
            if ($metadata === null) {
                return new JsonResult([
                    'error' => 'Model not found',
                    'model_name' => $modelName,
                    'version' => $versionString,
                ], Status::NOT_FOUND);
            }

            // Unregister
            $this->registry->unregister($modelName, $version);

            return new JsonResult([
                'message' => 'Model unregistered successfully',
                'model_name' => $modelName,
                'version' => $versionString,
            ]);
        } catch (\InvalidArgumentException $e) {
            return new JsonResult([
                'error' => 'Invalid version format',
                'message' => $e->getMessage(),
            ], Status::BAD_REQUEST);
        }
    }
}
