<?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\ABTestingService;
use App\Framework\MachineLearning\ModelManagement\ModelRegistry;
use App\Framework\MachineLearning\ModelManagement\ValueObjects\ABTestConfig;
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 A/B Testing API Controller
 *
 * RESTful API endpoints for A/B testing machine learning models:
 * - Start A/B tests
 * - Get test results
 * - Generate rollout plans
 * - Calculate sample sizes
 */
#[ApiSecurity('bearerAuth')]
final readonly class MLABTestingController
{
    public function __construct(
        private ABTestingService $abTesting,
        private ModelRegistry $registry
    ) {}

    #[Route(path: '/api/ml/ab-test', method: Method::POST)]
    #[ApiEndpoint(
        summary: 'Start A/B test',
        description: 'Create and start an A/B test comparing two model versions',
        tags: ['Machine Learning'],
    )]
    #[ApiRequestBody(
        description: 'A/B test configuration',
        required: true,
        example: [
            'model_name' => 'fraud-detector',
            'version_a' => '1.0.0',
            'version_b' => '2.0.0',
            'traffic_split_a' => 0.5,
            'primary_metric' => 'accuracy',
            'minimum_improvement' => 0.05,
        ],
    )]
    #[ApiResponse(
        statusCode: 201,
        description: 'A/B test created successfully',
        example: [
            'test_id' => 'test_123',
            'model_name' => 'fraud-detector',
            'version_a' => '1.0.0',
            'version_b' => '2.0.0',
            'traffic_split' => [
                'version_a' => 0.5,
                'version_b' => 0.5,
            ],
            'status' => 'running',
            'created_at' => '2024-01-01T00:00:00Z',
        ],
    )]
    #[ApiResponse(
        statusCode: 400,
        description: 'Invalid test configuration',
    )]
    public function startTest(HttpRequest $request): JsonResult
    {
        try {
            $data = $request->parsedBody->toArray();

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

            // Parse versions
            $versionA = Version::fromString($data['version_a']);
            $versionB = Version::fromString($data['version_b']);

            // Verify models exist
            $metadataA = $this->registry->get($data['model_name'], $versionA);
            $metadataB = $this->registry->get($data['model_name'], $versionB);

            if ($metadataA === null) {
                return new JsonResult([
                    'error' => 'Version A not found',
                    'model_name' => $data['model_name'],
                    'version' => $data['version_a'],
                ], Status::NOT_FOUND);
            }

            if ($metadataB === null) {
                return new JsonResult([
                    'error' => 'Version B not found',
                    'model_name' => $data['model_name'],
                    'version' => $data['version_b'],
                ], Status::NOT_FOUND);
            }

            // Create A/B test config
            $config = new ABTestConfig(
                modelName: $data['model_name'],
                versionA: $versionA,
                versionB: $versionB,
                trafficSplitA: (float) ($data['traffic_split_a'] ?? 0.5),
                primaryMetric: $data['primary_metric'] ?? 'accuracy',
                minimumImprovement: (float) ($data['minimum_improvement'] ?? 0.05),
                significanceLevel: (float) ($data['significance_level'] ?? 0.05)
            );

            // Generate test ID (in production, store in database)
            $testId = 'test_' . bin2hex(random_bytes(8));

            return new JsonResult([
                'test_id' => $testId,
                'model_name' => $config->modelName,
                'version_a' => $config->versionA->toString(),
                'version_b' => $config->versionB->toString(),
                'traffic_split' => [
                    'version_a' => $config->trafficSplitA,
                    'version_b' => 1.0 - $config->trafficSplitA,
                ],
                'primary_metric' => $config->primaryMetric,
                'minimum_improvement' => $config->minimumImprovement,
                'status' => 'running',
                'description' => $config->getDescription(),
                'created_at' => date('c'),
            ], Status::CREATED);
        } catch (\InvalidArgumentException $e) {
            return new JsonResult([
                'error' => 'Invalid test configuration',
                'message' => $e->getMessage(),
            ], Status::BAD_REQUEST);
        }
    }

    #[Route(path: '/api/ml/ab-test/compare', method: Method::POST)]
    #[ApiEndpoint(
        summary: 'Compare model versions',
        description: 'Compare performance of two model versions and get winner recommendation',
        tags: ['Machine Learning'],
    )]
    #[ApiRequestBody(
        description: 'Model comparison configuration',
        required: true,
        example: [
            'model_name' => 'fraud-detector',
            'version_a' => '1.0.0',
            'version_b' => '2.0.0',
            'primary_metric' => 'f1_score',
            'minimum_improvement' => 0.05,
        ],
    )]
    #[ApiResponse(
        statusCode: 200,
        description: 'Comparison completed successfully',
        example: [
            'winner' => 'B',
            'statistically_significant' => true,
            'metrics_difference' => [
                'accuracy' => 0.07,
                'f1_score' => 0.08,
            ],
            'primary_metric_improvement' => 8.5,
            'recommendation' => 'Version B wins with 8.5% improvement - deploy new version',
            'summary' => 'Version B significantly outperforms Version A',
        ],
    )]
    #[ApiResponse(
        statusCode: 404,
        description: 'Model version not found',
    )]
    public function compareVersions(HttpRequest $request): JsonResult
    {
        try {
            $data = $request->parsedBody->toArray();

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

            $versionA = Version::fromString($data['version_a']);
            $versionB = Version::fromString($data['version_b']);

            $config = new ABTestConfig(
                modelName: $data['model_name'],
                versionA: $versionA,
                versionB: $versionB,
                trafficSplitA: 0.5,
                primaryMetric: $data['primary_metric'] ?? 'accuracy',
                minimumImprovement: (float) ($data['minimum_improvement'] ?? 0.05)
            );

            // Run comparison
            $result = $this->abTesting->runTest($config);

            return new JsonResult([
                'winner' => $result->winner,
                'statistically_significant' => $result->isStatisticallySignificant,
                'metrics_difference' => $result->metricsDifference,
                'primary_metric_improvement' => $result->getPrimaryMetricImprovementPercent(),
                'recommendation' => $result->recommendation,
                'summary' => $result->getSummary(),
                'should_deploy_version_b' => $result->shouldDeployVersionB(),
                'is_inconclusive' => $result->isInconclusive(),
            ]);
        } catch (\InvalidArgumentException $e) {
            return new JsonResult([
                'error' => 'Invalid comparison parameters',
                'message' => $e->getMessage(),
            ], Status::BAD_REQUEST);
        }
    }

    #[Route(path: '/api/ml/ab-test/rollout-plan', method: Method::POST)]
    #[ApiEndpoint(
        summary: 'Generate rollout plan',
        description: 'Generate a gradual rollout plan for deploying a new model version',
        tags: ['Machine Learning'],
    )]
    #[ApiRequestBody(
        description: 'Rollout configuration',
        required: true,
        example: [
            'model_name' => 'fraud-detector',
            'current_version' => '1.0.0',
            'new_version' => '2.0.0',
            'steps' => 5,
        ],
    )]
    #[ApiResponse(
        statusCode: 200,
        description: 'Rollout plan generated successfully',
        example: [
            'model_name' => 'fraud-detector',
            'current_version' => '1.0.0',
            'new_version' => '2.0.0',
            'rollout_stages' => [
                [
                    'stage' => 1,
                    'current_version_traffic' => 80,
                    'new_version_traffic' => 20,
                ],
                [
                    'stage' => 2,
                    'current_version_traffic' => 60,
                    'new_version_traffic' => 40,
                ],
            ],
        ],
    )]
    public function generateRolloutPlan(HttpRequest $request): JsonResult
    {
        try {
            $data = $request->parsedBody->toArray();

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

            $steps = (int) ($data['steps'] ?? 5);

            if ($steps < 2 || $steps > 10) {
                return new JsonResult([
                    'error' => 'Steps must be between 2 and 10',
                ], Status::BAD_REQUEST);
            }

            // Generate rollout plan
            $plan = $this->abTesting->generateRolloutPlan($steps);

            // Format response
            $stages = [];
            foreach ($plan as $stage => $newVersionTraffic) {
                $stages[] = [
                    'stage' => $stage,
                    'current_version_traffic' => (int) ((1.0 - $newVersionTraffic) * 100),
                    'new_version_traffic' => (int) ($newVersionTraffic * 100),
                ];
            }

            return new JsonResult([
                'model_name' => $data['model_name'],
                'current_version' => $data['current_version'],
                'new_version' => $data['new_version'],
                'total_stages' => $steps,
                'rollout_stages' => $stages,
                'recommendation' => 'Monitor performance at each stage before proceeding to next',
            ]);
        } catch (\InvalidArgumentException $e) {
            return new JsonResult([
                'error' => 'Invalid rollout configuration',
                'message' => $e->getMessage(),
            ], Status::BAD_REQUEST);
        }
    }

    #[Route(path: '/api/ml/ab-test/sample-size', method: Method::GET)]
    #[ApiEndpoint(
        summary: 'Calculate required sample size',
        description: 'Calculate the required sample size for statistically significant A/B test',
        tags: ['Machine Learning'],
    )]
    #[ApiParameter(
        name: 'confidence_level',
        in: 'query',
        description: 'Confidence level (0.90, 0.95, 0.99)',
        required: false,
        type: 'number',
        example: 0.95,
    )]
    #[ApiParameter(
        name: 'margin_of_error',
        in: 'query',
        description: 'Margin of error (typically 0.01-0.10)',
        required: false,
        type: 'number',
        example: 0.05,
    )]
    #[ApiResponse(
        statusCode: 200,
        description: 'Sample size calculated successfully',
        example: [
            'required_samples_per_version' => 385,
            'total_samples_needed' => 770,
            'confidence_level' => 0.95,
            'margin_of_error' => 0.05,
            'recommendation' => 'Collect at least 385 predictions per version',
        ],
    )]
    public function calculateSampleSize(HttpRequest $request): JsonResult
    {
        $confidenceLevel = $request->query->getFloat('confidence_level', 0.95);
        $marginOfError = $request->query->getFloat('margin_of_error', 0.05);

        // Validate parameters
        if ($confidenceLevel < 0.5 || $confidenceLevel > 0.99) {
            return new JsonResult([
                'error' => 'Confidence level must be between 0.5 and 0.99',
            ], Status::BAD_REQUEST);
        }

        if ($marginOfError < 0.01 || $marginOfError > 0.20) {
            return new JsonResult([
                'error' => 'Margin of error must be between 0.01 and 0.20',
            ], Status::BAD_REQUEST);
        }

        // Calculate sample size
        $samplesPerVersion = $this->abTesting->calculateRequiredSampleSize(
            $confidenceLevel,
            $marginOfError
        );

        return new JsonResult([
            'required_samples_per_version' => $samplesPerVersion,
            'total_samples_needed' => $samplesPerVersion * 2,
            'confidence_level' => $confidenceLevel,
            'margin_of_error' => $marginOfError,
            'confidence_level_percent' => ($confidenceLevel * 100) . '%',
            'margin_of_error_percent' => ($marginOfError * 100) . '%',
            'recommendation' => "Collect at least {$samplesPerVersion} predictions per version for statistically significant results",
        ]);
    }

    #[Route(path: '/api/ml/ab-test/select-version', method: Method::POST)]
    #[ApiEndpoint(
        summary: 'Select model version for traffic routing',
        description: 'Randomly select a model version based on A/B test traffic split configuration',
        tags: ['Machine Learning'],
    )]
    #[ApiRequestBody(
        description: 'Traffic routing configuration',
        required: true,
        example: [
            'model_name' => 'fraud-detector',
            'version_a' => '1.0.0',
            'version_b' => '2.0.0',
            'traffic_split_a' => 0.8,
        ],
    )]
    #[ApiResponse(
        statusCode: 200,
        description: 'Version selected successfully',
        example: [
            'selected_version' => '2.0.0',
            'model_name' => 'fraud-detector',
            'traffic_split' => [
                'version_a' => 0.8,
                'version_b' => 0.2,
            ],
        ],
    )]
    public function selectVersion(HttpRequest $request): JsonResult
    {
        try {
            $data = $request->parsedBody->toArray();

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

            $versionA = Version::fromString($data['version_a']);
            $versionB = Version::fromString($data['version_b']);

            $config = new ABTestConfig(
                modelName: $data['model_name'],
                versionA: $versionA,
                versionB: $versionB,
                trafficSplitA: (float) ($data['traffic_split_a'] ?? 0.5),
                primaryMetric: 'accuracy'
            );

            // Select version based on traffic split
            $selectedVersion = $this->abTesting->selectVersion($config);

            return new JsonResult([
                'selected_version' => $selectedVersion->toString(),
                'model_name' => $config->modelName,
                'traffic_split' => [
                    'version_a' => $config->trafficSplitA,
                    'version_b' => 1.0 - $config->trafficSplitA,
                ],
            ]);
        } catch (\InvalidArgumentException $e) {
            return new JsonResult([
                'error' => 'Invalid routing configuration',
                'message' => $e->getMessage(),
            ], Status::BAD_REQUEST);
        }
    }
}
