<?php

declare(strict_types=1);

namespace App\Application\Api\MachineLearning;

use App\Framework\Attributes\Route;
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\AutoTuningEngine;
use App\Framework\MachineLearning\ModelManagement\ModelRegistry;
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 Auto-Tuning API Controller
 *
 * RESTful API endpoints for automatic ML model optimization:
 * - Threshold optimization
 * - Adaptive threshold adjustment
 * - Precision-recall trade-off optimization
 * - Hyperparameter tuning
 */
#[ApiSecurity('bearerAuth')]
final readonly class MLAutoTuningController
{
    public function __construct(
        private AutoTuningEngine $autoTuning,
        private ModelRegistry $registry
    ) {}

    #[Route(path: '/api/ml/optimize/threshold', method: Method::POST)]
    #[ApiEndpoint(
        summary: 'Optimize model threshold',
        description: 'Find optimal threshold using grid search to maximize specified metric',
        tags: ['Machine Learning'],
    )]
    #[ApiRequestBody(
        description: 'Threshold optimization configuration',
        required: true,
        example: [
            'model_name' => 'fraud-detector',
            'version' => '1.0.0',
            'metric_to_optimize' => 'f1_score',
            'threshold_range' => [0.5, 0.9],
            'step' => 0.05,
        ],
    )]
    #[ApiResponse(
        statusCode: 200,
        description: 'Threshold optimization completed',
        example: [
            'optimal_threshold' => 0.75,
            'optimal_metric_value' => 0.92,
            'current_threshold' => 0.7,
            'current_metric_value' => 0.89,
            'improvement_percent' => 3.37,
            'recommendation' => 'MODERATE IMPROVEMENT: Consider updating threshold from 0.70 to 0.75 (3.4% gain)',
            'tested_thresholds' => 9,
        ],
    )]
    #[ApiResponse(
        statusCode: 404,
        description: 'Model not found',
    )]
    #[ApiResponse(
        statusCode: 400,
        description: 'Insufficient data for optimization (requires minimum 100 predictions)',
    )]
    public function optimizeThreshold(HttpRequest $request): JsonResult
    {
        try {
            $data = $request->parsedBody->toArray();

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

            $version = Version::fromString($data['version']);

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

            // Optimize threshold
            $result = $this->autoTuning->optimizeThreshold(
                modelName: $data['model_name'],
                version: $version,
                metricToOptimize: $data['metric_to_optimize'] ?? 'f1_score',
                thresholdRange: $data['threshold_range'] ?? [0.5, 0.9],
                step: (float) ($data['step'] ?? 0.05)
            );

            return new JsonResult([
                'optimal_threshold' => $result['optimal_threshold'],
                'optimal_metric_value' => $result['optimal_metric_value'],
                'current_threshold' => $result['current_threshold'],
                'current_metric_value' => $result['current_metric_value'],
                'improvement_percent' => $result['improvement_percent'],
                'metric_optimized' => $result['metric_optimized'],
                'recommendation' => $result['recommendation'],
                'tested_thresholds' => count($result['all_results']),
                'all_results' => $result['all_results'],
            ]);
        } catch (\RuntimeException $e) {
            return new JsonResult([
                'error' => 'Optimization failed',
                'message' => $e->getMessage(),
            ], Status::BAD_REQUEST);
        } catch (\InvalidArgumentException $e) {
            return new JsonResult([
                'error' => 'Invalid optimization parameters',
                'message' => $e->getMessage(),
            ], Status::BAD_REQUEST);
        }
    }

    #[Route(path: '/api/ml/optimize/adaptive-threshold', method: Method::POST)]
    #[ApiEndpoint(
        summary: 'Adaptive threshold adjustment',
        description: 'Dynamically adjust threshold based on false positive/negative rates',
        tags: ['Machine Learning'],
    )]
    #[ApiRequestBody(
        description: 'Model identification',
        required: true,
        example: [
            'model_name' => 'fraud-detector',
            'version' => '1.0.0',
        ],
    )]
    #[ApiResponse(
        statusCode: 200,
        description: 'Adaptive adjustment calculated',
        example: [
            'recommended_threshold' => 0.75,
            'current_threshold' => 0.7,
            'adjustment_reason' => 'High false positive rate - increasing threshold to reduce false alarms',
            'current_fp_rate' => 0.12,
            'current_fn_rate' => 0.05,
            'expected_improvement' => [
                'accuracy' => 0.03,
                'precision' => 0.05,
                'recall' => -0.02,
            ],
        ],
    )]
    #[ApiResponse(
        statusCode: 404,
        description: 'Model not found',
    )]
    public function adaptiveThresholdAdjustment(HttpRequest $request): JsonResult
    {
        try {
            $data = $request->parsedBody->toArray();

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

            $version = Version::fromString($data['version']);

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

            // Calculate adaptive adjustment
            $result = $this->autoTuning->adaptiveThresholdAdjustment(
                $data['model_name'],
                $version
            );

            return new JsonResult([
                'recommended_threshold' => $result['recommended_threshold'],
                'current_threshold' => $result['current_threshold'],
                'adjustment_reason' => $result['adjustment_reason'],
                'current_fp_rate' => $result['current_fp_rate'],
                'current_fn_rate' => $result['current_fn_rate'],
                'expected_improvement' => $result['expected_improvement'],
            ]);
        } catch (\InvalidArgumentException $e) {
            return new JsonResult([
                'error' => 'Invalid adjustment parameters',
                'message' => $e->getMessage(),
            ], Status::BAD_REQUEST);
        }
    }

    #[Route(path: '/api/ml/optimize/precision-recall', method: Method::POST)]
    #[ApiEndpoint(
        summary: 'Optimize precision-recall trade-off',
        description: 'Find threshold that achieves target precision while maximizing recall',
        tags: ['Machine Learning'],
    )]
    #[ApiRequestBody(
        description: 'Precision-recall optimization configuration',
        required: true,
        example: [
            'model_name' => 'fraud-detector',
            'version' => '1.0.0',
            'target_precision' => 0.95,
            'threshold_range' => [0.5, 0.99],
        ],
    )]
    #[ApiResponse(
        statusCode: 200,
        description: 'Precision-recall optimization completed',
        example: [
            'optimal_threshold' => 0.82,
            'achieved_precision' => 0.95,
            'achieved_recall' => 0.78,
            'f1_score' => 0.86,
            'target_precision' => 0.95,
        ],
    )]
    #[ApiResponse(
        statusCode: 404,
        description: 'Model not found',
    )]
    public function optimizePrecisionRecall(HttpRequest $request): JsonResult
    {
        try {
            $data = $request->parsedBody->toArray();

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

            $version = Version::fromString($data['version']);

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

            // Optimize precision-recall trade-off
            $result = $this->autoTuning->optimizePrecisionRecallTradeoff(
                modelName: $data['model_name'],
                version: $version,
                targetPrecision: (float) ($data['target_precision'] ?? 0.95),
                thresholdRange: $data['threshold_range'] ?? [0.5, 0.99]
            );

            return new JsonResult([
                'optimal_threshold' => $result['optimal_threshold'],
                'achieved_precision' => $result['achieved_precision'],
                'achieved_recall' => $result['achieved_recall'],
                'f1_score' => $result['f1_score'],
                'target_precision' => (float) ($data['target_precision'] ?? 0.95),
                'recommendation' => sprintf(
                    'Use threshold %.2f to achieve %.1f%% precision with %.1f%% recall',
                    $result['optimal_threshold'],
                    $result['achieved_precision'] * 100,
                    $result['achieved_recall'] * 100
                ),
            ]);
        } catch (\InvalidArgumentException $e) {
            return new JsonResult([
                'error' => 'Invalid optimization parameters',
                'message' => $e->getMessage(),
            ], Status::BAD_REQUEST);
        }
    }

    #[Route(path: '/api/ml/optimize/apply-threshold', method: Method::POST)]
    #[ApiEndpoint(
        summary: 'Apply optimized threshold',
        description: 'Update model configuration with optimized threshold',
        tags: ['Machine Learning'],
    )]
    #[ApiRequestBody(
        description: 'Threshold application configuration',
        required: true,
        example: [
            'model_name' => 'fraud-detector',
            'version' => '1.0.0',
            'new_threshold' => 0.75,
        ],
    )]
    #[ApiResponse(
        statusCode: 200,
        description: 'Threshold applied successfully',
        example: [
            'model_name' => 'fraud-detector',
            'version' => '1.0.0',
            'old_threshold' => 0.7,
            'new_threshold' => 0.75,
            'message' => 'Threshold updated successfully',
        ],
    )]
    #[ApiResponse(
        statusCode: 404,
        description: 'Model not found',
    )]
    public function applyThreshold(HttpRequest $request): JsonResult
    {
        try {
            $data = $request->parsedBody->toArray();

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

            $version = Version::fromString($data['version']);
            $newThreshold = (float) $data['new_threshold'];

            // Validate threshold range
            if ($newThreshold < 0.0 || $newThreshold > 1.0) {
                return new JsonResult([
                    'error' => 'Threshold must be between 0.0 and 1.0',
                ], Status::BAD_REQUEST);
            }

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

            $oldThreshold = $metadata->configuration['threshold'] ?? null;

            // Update configuration
            $updatedMetadata = $metadata->withConfiguration([
                ...$metadata->configuration,
                'threshold' => $newThreshold,
                'threshold_updated_at' => date('c'),
                'threshold_update_reason' => $data['reason'] ?? 'Manual optimization',
            ]);

            $this->registry->update($updatedMetadata);

            return new JsonResult([
                'model_name' => $data['model_name'],
                'version' => $version->toString(),
                'old_threshold' => $oldThreshold,
                'new_threshold' => $newThreshold,
                'message' => 'Threshold updated successfully',
                'updated_at' => date('c'),
            ]);
        } catch (\InvalidArgumentException $e) {
            return new JsonResult([
                'error' => 'Invalid threshold update',
                'message' => $e->getMessage(),
            ], Status::BAD_REQUEST);
        }
    }
}
