<?php

declare(strict_types=1);

namespace App\Framework\Monitoring;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\Core\ValueObjects\Timestamp;

/**
 * Metrics value object for error boundary monitoring
 */
final readonly class ErrorBoundaryMetrics
{
    public function __construct(
        public string $boundaryName,
        public int $totalExecutions,
        public int $totalSuccesses,
        public int $totalFallbacks,
        public int $totalErrors,
        public Duration $averageExecutionTime,
        public Duration $peakExecutionTime,
        public Timestamp $lastExecution,
        public int $consecutiveFailures,
        public array $errorTypes = [], // Array of error class names with counts
    ) {
    }

    /**
     * Get success rate as percentage
     */
    public function getSuccessRate(): Percentage
    {
        if ($this->totalExecutions === 0) {
            return Percentage::fromFloat(0.0);
        }

        return Percentage::fromFloat($this->totalSuccesses / $this->totalExecutions);
    }

    /**
     * Get fallback rate as percentage
     */
    public function getFallbackRate(): Percentage
    {
        if ($this->totalExecutions === 0) {
            return Percentage::fromFloat(0.0);
        }

        return Percentage::fromFloat($this->totalFallbacks / $this->totalExecutions);
    }

    /**
     * Get error rate as percentage
     */
    public function getErrorRate(): Percentage
    {
        if ($this->totalExecutions === 0) {
            return Percentage::fromFloat(0.0);
        }

        return Percentage::fromFloat($this->totalErrors / $this->totalExecutions);
    }

    /**
     * Check if boundary is healthy
     */
    public function isHealthy(): bool
    {
        return $this->getFallbackRate()->toFloat() < 0.1 && // Less than 10% fallback rate
               $this->getErrorRate()->toFloat() < 0.05; // Less than 5% error rate
    }

    /**
     * Check if boundary is degraded
     */
    public function isDegraded(): bool
    {
        return ($this->getFallbackRate()->toFloat() >= 0.1 && $this->getFallbackRate()->toFloat() < 0.25) ||
               ($this->getErrorRate()->toFloat() >= 0.05 && $this->getErrorRate()->toFloat() < 0.15);
    }

    /**
     * Check if boundary is failed
     */
    public function isFailed(): bool
    {
        return $this->getFallbackRate()->toFloat() >= 0.25 || // More than 25% fallback rate
               $this->getErrorRate()->toFloat() >= 0.15; // More than 15% error rate
    }

    /**
     * Get health status string
     */
    public function getHealthStatus(): string
    {
        if ($this->isFailed()) {
            return 'failed';
        }

        if ($this->isDegraded()) {
            return 'degraded';
        }

        return 'healthy';
    }

    /**
     * Get time since last execution
     */
    public function getTimeSinceLastExecution(Timestamp $currentTime): Duration
    {
        return $this->lastExecution->diff($currentTime);
    }

    /**
     * Get most common error type
     */
    public function getMostCommonErrorType(): ?string
    {
        if (empty($this->errorTypes)) {
            return null;
        }

        $maxCount = 0;
        $mostCommon = null;

        foreach ($this->errorTypes as $errorType => $count) {
            if ($count > $maxCount) {
                $maxCount = $count;
                $mostCommon = $errorType;
            }
        }

        return $mostCommon;
    }

    /**
     * Get error type distribution
     */
    public function getErrorTypeDistribution(): array
    {
        if (empty($this->errorTypes) || $this->totalErrors === 0) {
            return [];
        }

        $distribution = [];
        foreach ($this->errorTypes as $errorType => $count) {
            $distribution[$errorType] = [
                'count' => $count,
                'percentage' => ($count / $this->totalErrors) * 100,
            ];
        }

        // Sort by count descending
        uasort($distribution, fn ($a, $b) => $b['count'] <=> $a['count']);

        return $distribution;
    }

    /**
     * Convert to array for serialization
     */
    public function toArray(): array
    {
        return [
            'boundary_name' => $this->boundaryName,
            'total_executions' => $this->totalExecutions,
            'total_successes' => $this->totalSuccesses,
            'total_fallbacks' => $this->totalFallbacks,
            'total_errors' => $this->totalErrors,
            'success_rate' => $this->getSuccessRate()->toFloat(),
            'fallback_rate' => $this->getFallbackRate()->toFloat(),
            'error_rate' => $this->getErrorRate()->toFloat(),
            'average_execution_time_ms' => $this->averageExecutionTime->toMilliseconds(),
            'peak_execution_time_ms' => $this->peakExecutionTime->toMilliseconds(),
            'last_execution' => $this->lastExecution->toIsoString(),
            'consecutive_failures' => $this->consecutiveFailures,
            'error_types' => $this->errorTypes,
            'most_common_error_type' => $this->getMostCommonErrorType(),
            'error_type_distribution' => $this->getErrorTypeDistribution(),
            'health_status' => $this->getHealthStatus(),
            'is_healthy' => $this->isHealthy(),
            'is_degraded' => $this->isDegraded(),
            'is_failed' => $this->isFailed(),
        ];
    }

    /**
     * Create from array
     */
    public static function fromArray(array $data): self
    {
        return new self(
            boundaryName: $data['boundary_name'],
            totalExecutions: $data['total_executions'],
            totalSuccesses: $data['total_successes'],
            totalFallbacks: $data['total_fallbacks'],
            totalErrors: $data['total_errors'],
            averageExecutionTime: Duration::fromMilliseconds($data['average_execution_time_ms']),
            peakExecutionTime: Duration::fromMilliseconds($data['peak_execution_time_ms']),
            lastExecution: Timestamp::fromFloat(strtotime($data['last_execution'])),
            consecutiveFailures: $data['consecutive_failures'],
            errorTypes: $data['error_types'] ?? [],
        );
    }
}
