<?php

declare(strict_types=1);

namespace App\Framework\MachineLearning\ModelManagement\ValueObjects;

use App\Framework\MachineLearning\ModelManagement\ValueObjects\ModelMetadata;
use App\Framework\Core\ValueObjects\Version;

/**
 * A/B Test Result Value Object
 *
 * Immutable result of ML model A/B testing experiment with performance comparison.
 *
 * Usage:
 * ```php
 * $result = new ABTestResult(
 *     config: $config,
 *     metadataA: $modelA,
 *     metadataB: $modelB,
 *     metricsDifference: ['accuracy' => 0.03, 'precision' => 0.02],
 *     winner: 'B',
 *     isStatisticallySignificant: true,
 *     recommendation: 'Deploy version B - 3% accuracy improvement'
 * );
 * ```
 */
final readonly class ABTestResult
{
    /**
     * @param ABTestConfig $config Test configuration
     * @param ModelMetadata $metadataA Version A metadata
     * @param ModelMetadata $metadataB Version B metadata
     * @param array<string, float> $metricsDifference Metric name => difference (B - A)
     * @param string $winner 'A', 'B', or 'tie'
     * @param bool $isStatisticallySignificant Whether results are statistically significant
     * @param string $recommendation Actionable recommendation based on results
     */
    public function __construct(
        public ABTestConfig $config,
        public ModelMetadata $metadataA,
        public ModelMetadata $metadataB,
        public array $metricsDifference,
        public string $winner,
        public bool $isStatisticallySignificant,
        public string $recommendation
    ) {
        if (!in_array($winner, ['A', 'B', 'tie'], true)) {
            throw new \InvalidArgumentException(
                "Winner must be 'A', 'B', or 'tie'"
            );
        }
    }

    /**
     * Get the winning version
     */
    public function getWinningVersion(): ?Version
    {
        return match ($this->winner) {
            'A' => $this->metadataA->version,
            'B' => $this->metadataB->version,
            'tie' => null,
        };
    }

    /**
     * Get the winning model metadata
     */
    public function getWinningMetadata(): ?ModelMetadata
    {
        return match ($this->winner) {
            'A' => $this->metadataA,
            'B' => $this->metadataB,
            'tie' => null,
        };
    }

    /**
     * Check if version B is the winner
     */
    public function shouldDeployVersionB(): bool
    {
        return $this->winner === 'B' && $this->isStatisticallySignificant;
    }

    /**
     * Check if version A is the winner (keep current)
     */
    public function shouldKeepVersionA(): bool
    {
        return $this->winner === 'A' && $this->isStatisticallySignificant;
    }

    /**
     * Check if test is inconclusive (tie or not significant)
     */
    public function isInconclusive(): bool
    {
        return $this->winner === 'tie' || !$this->isStatisticallySignificant;
    }

    /**
     * Get primary metric improvement
     */
    public function getPrimaryMetricImprovement(): float
    {
        return $this->metricsDifference[$this->config->primaryMetric] ?? 0.0;
    }

    /**
     * Get primary metric improvement as percentage
     */
    public function getPrimaryMetricImprovementPercent(): float
    {
        return $this->getPrimaryMetricImprovement() * 100;
    }

    /**
     * Get all positive improvements (version B better than A)
     */
    public function getPositiveImprovements(): array
    {
        return array_filter(
            $this->metricsDifference,
            fn(float $diff) => $diff > 0
        );
    }

    /**
     * Get all negative changes (version B worse than A)
     */
    public function getNegativeChanges(): array
    {
        return array_filter(
            $this->metricsDifference,
            fn(float $diff) => $diff < 0
        );
    }

    /**
     * Get metrics summary
     */
    public function getMetricsSummary(): array
    {
        return [
            'version_a' => [
                'version' => $this->metadataA->version->toString(),
                'accuracy' => $this->metadataA->getAccuracy(),
                'precision' => $this->metadataA->getPrecision(),
                'recall' => $this->metadataA->getRecall(),
                'f1_score' => $this->metadataA->getF1Score(),
            ],
            'version_b' => [
                'version' => $this->metadataB->version->toString(),
                'accuracy' => $this->metadataB->getAccuracy(),
                'precision' => $this->metadataB->getPrecision(),
                'recall' => $this->metadataB->getRecall(),
                'f1_score' => $this->metadataB->getF1Score(),
            ],
            'differences' => $this->metricsDifference,
            'primary_metric_improvement' => $this->getPrimaryMetricImprovementPercent(),
        ];
    }

    /**
     * Get confidence level
     */
    public function getConfidenceLevel(): float
    {
        return $this->config->getConfidenceLevel();
    }

    /**
     * Get test summary as string
     */
    public function getSummary(): string
    {
        $winner = match ($this->winner) {
            'A' => "Version A ({$this->metadataA->version->toString()})",
            'B' => "Version B ({$this->metadataB->version->toString()})",
            'tie' => 'No clear winner',
        };

        $significance = $this->isStatisticallySignificant
            ? 'statistically significant'
            : 'NOT statistically significant';

        $improvement = $this->getPrimaryMetricImprovementPercent();
        $improvementStr = sprintf('%+.2f%%', $improvement);

        return sprintf(
            '%s wins with %s improvement in %s (%s)',
            $winner,
            $improvementStr,
            $this->config->primaryMetric,
            $significance
        );
    }

    /**
     * Convert to array for serialization
     */
    public function toArray(): array
    {
        return [
            'config' => $this->config->toArray(),
            'version_a' => [
                'version' => $this->metadataA->version->toString(),
                'metrics' => $this->metadataA->performanceMetrics,
            ],
            'version_b' => [
                'version' => $this->metadataB->version->toString(),
                'metrics' => $this->metadataB->performanceMetrics,
            ],
            'metrics_difference' => $this->metricsDifference,
            'winner' => $this->winner,
            'winning_version' => $this->getWinningVersion()?->toString(),
            'is_statistically_significant' => $this->isStatisticallySignificant,
            'recommendation' => $this->recommendation,
            'summary' => $this->getSummary(),
            'should_deploy_b' => $this->shouldDeployVersionB(),
            'is_inconclusive' => $this->isInconclusive(),
            'primary_metric_improvement' => $this->getPrimaryMetricImprovementPercent(),
            'positive_improvements' => $this->getPositiveImprovements(),
            'negative_changes' => $this->getNegativeChanges(),
        ];
    }

    /**
     * Create from array
     */
    public static function fromArray(
        array $data,
        ModelMetadata $metadataA,
        ModelMetadata $metadataB
    ): self {
        return new self(
            config: ABTestConfig::fromArray($data['config']),
            metadataA: $metadataA,
            metadataB: $metadataB,
            metricsDifference: $data['metrics_difference'],
            winner: $data['winner'],
            isStatisticallySignificant: $data['is_statistically_significant'],
            recommendation: $data['recommendation']
        );
    }
}
