<?php

declare(strict_types=1);

namespace App\Framework\ErrorBoundaries\Metrics;

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

/**
 * Metrics for error boundary operations
 */
final readonly class BoundaryMetrics
{
    public function __construct(
        public string $boundaryName,
        public int $totalExecutions = 0,
        public int $successfulExecutions = 0,
        public int $failedExecutions = 0,
        public int $fallbackExecutions = 0,
        public int $timeoutExecutions = 0,
        public int $circuitBreakerTrips = 0,
        public Duration $totalExecutionTime = new Duration(0.0),
        public Duration $averageExecutionTime = new Duration(0.0),
        public Duration $maxExecutionTime = new Duration(0.0),
        public Duration $minExecutionTime = new Duration(0.0),
        public ?Timestamp $lastSuccessTime = null,
        public ?Timestamp $lastFailureTime = null,
        public ?Timestamp $lastFallbackTime = null,
        public ?Timestamp $createdAt = null,
        public ?Timestamp $updatedAt = null,
    ) {
    }

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

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

    /**
     * Calculate failure rate
     */
    public function getFailureRate(): Percentage
    {
        if ($this->totalExecutions === 0) {
            return Percentage::fromFloat(0.0);
        }

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

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

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

    /**
     * Calculate timeout rate
     */
    public function getTimeoutRate(): Percentage
    {
        if ($this->totalExecutions === 0) {
            return Percentage::fromFloat(0.0);
        }

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

    /**
     * Check if boundary is healthy (success rate > 90%)
     */
    public function isHealthy(): bool
    {
        return $this->getSuccessRate()->toFloat() > 0.9;
    }

    /**
     * Check if circuit breaker is frequently tripping
     */
    public function hasFrequentCircuitBreakerTrips(): bool
    {
        if ($this->totalExecutions === 0) {
            return false;
        }

        $tripRate = $this->circuitBreakerTrips / $this->totalExecutions;

        return $tripRate > 0.1; // More than 10% of executions result in circuit breaker trips
    }

    /**
     * Get health status
     */
    public function getHealthStatus(): BoundaryHealthStatus
    {
        if ($this->isHealthy() && ! $this->hasFrequentCircuitBreakerTrips()) {
            return BoundaryHealthStatus::HEALTHY;
        }

        if ($this->getSuccessRate()->toFloat() > 0.7) {
            return BoundaryHealthStatus::WARNING;
        }

        return BoundaryHealthStatus::CRITICAL;
    }

    /**
     * Record successful execution
     */
    public function recordSuccess(Duration $executionTime): self
    {
        $newTotalTime = $this->totalExecutionTime->add($executionTime);
        $newTotalExecutions = $this->totalExecutions + 1;
        $newAverageTime = Duration::fromMilliseconds(
            $newTotalTime->toMilliseconds() / $newTotalExecutions
        );

        return new self(
            boundaryName: $this->boundaryName,
            totalExecutions: $newTotalExecutions,
            successfulExecutions: $this->successfulExecutions + 1,
            failedExecutions: $this->failedExecutions,
            fallbackExecutions: $this->fallbackExecutions,
            timeoutExecutions: $this->timeoutExecutions,
            circuitBreakerTrips: $this->circuitBreakerTrips,
            totalExecutionTime: $newTotalTime,
            averageExecutionTime: $newAverageTime,
            maxExecutionTime: $executionTime->isGreaterThan($this->maxExecutionTime)
                ? $executionTime
                : $this->maxExecutionTime,
            minExecutionTime: $this->minExecutionTime->isZero() || $executionTime->isLessThan($this->minExecutionTime)
                ? $executionTime
                : $this->minExecutionTime,
            lastSuccessTime: Timestamp::now(),
            lastFailureTime: $this->lastFailureTime,
            lastFallbackTime: $this->lastFallbackTime,
            createdAt: $this->createdAt ?? Timestamp::now(),
            updatedAt: Timestamp::now(),
        );
    }

    /**
     * Record failed execution
     */
    public function recordFailure(Duration $executionTime): self
    {
        $newTotalTime = $this->totalExecutionTime->add($executionTime);
        $newTotalExecutions = $this->totalExecutions + 1;
        $newAverageTime = Duration::fromMilliseconds(
            $newTotalTime->toMilliseconds() / $newTotalExecutions
        );

        return new self(
            boundaryName: $this->boundaryName,
            totalExecutions: $newTotalExecutions,
            successfulExecutions: $this->successfulExecutions,
            failedExecutions: $this->failedExecutions + 1,
            fallbackExecutions: $this->fallbackExecutions,
            timeoutExecutions: $this->timeoutExecutions,
            circuitBreakerTrips: $this->circuitBreakerTrips,
            totalExecutionTime: $newTotalTime,
            averageExecutionTime: $newAverageTime,
            maxExecutionTime: $executionTime->isGreaterThan($this->maxExecutionTime)
                ? $executionTime
                : $this->maxExecutionTime,
            minExecutionTime: $this->minExecutionTime->isZero() || $executionTime->isLessThan($this->minExecutionTime)
                ? $executionTime
                : $this->minExecutionTime,
            lastSuccessTime: $this->lastSuccessTime,
            lastFailureTime: Timestamp::now(),
            lastFallbackTime: $this->lastFallbackTime,
            createdAt: $this->createdAt ?? Timestamp::now(),
            updatedAt: Timestamp::now(),
        );
    }

    /**
     * Record fallback execution
     */
    public function recordFallback(): self
    {
        return new self(
            boundaryName: $this->boundaryName,
            totalExecutions: $this->totalExecutions + 1,
            successfulExecutions: $this->successfulExecutions,
            failedExecutions: $this->failedExecutions,
            fallbackExecutions: $this->fallbackExecutions + 1,
            timeoutExecutions: $this->timeoutExecutions,
            circuitBreakerTrips: $this->circuitBreakerTrips,
            totalExecutionTime: $this->totalExecutionTime,
            averageExecutionTime: $this->averageExecutionTime,
            maxExecutionTime: $this->maxExecutionTime,
            minExecutionTime: $this->minExecutionTime,
            lastSuccessTime: $this->lastSuccessTime,
            lastFailureTime: $this->lastFailureTime,
            lastFallbackTime: Timestamp::now(),
            createdAt: $this->createdAt ?? Timestamp::now(),
            updatedAt: Timestamp::now(),
        );
    }

    /**
     * Record timeout execution
     */
    public function recordTimeout(Duration $executionTime): self
    {
        $newTotalTime = $this->totalExecutionTime->add($executionTime);
        $newTotalExecutions = $this->totalExecutions + 1;
        $newAverageTime = Duration::fromMilliseconds(
            $newTotalTime->toMilliseconds() / $newTotalExecutions
        );

        return new self(
            boundaryName: $this->boundaryName,
            totalExecutions: $newTotalExecutions,
            successfulExecutions: $this->successfulExecutions,
            failedExecutions: $this->failedExecutions,
            fallbackExecutions: $this->fallbackExecutions,
            timeoutExecutions: $this->timeoutExecutions + 1,
            circuitBreakerTrips: $this->circuitBreakerTrips,
            totalExecutionTime: $newTotalTime,
            averageExecutionTime: $newAverageTime,
            maxExecutionTime: $executionTime->isGreaterThan($this->maxExecutionTime)
                ? $executionTime
                : $this->maxExecutionTime,
            minExecutionTime: $this->minExecutionTime->isZero() || $executionTime->isLessThan($this->minExecutionTime)
                ? $executionTime
                : $this->minExecutionTime,
            lastSuccessTime: $this->lastSuccessTime,
            lastFailureTime: $this->lastFailureTime,
            lastFallbackTime: $this->lastFallbackTime,
            createdAt: $this->createdAt ?? Timestamp::now(),
            updatedAt: Timestamp::now(),
        );
    }

    /**
     * Record circuit breaker trip
     */
    public function recordCircuitBreakerTrip(): self
    {
        return new self(
            boundaryName: $this->boundaryName,
            totalExecutions: $this->totalExecutions,
            successfulExecutions: $this->successfulExecutions,
            failedExecutions: $this->failedExecutions,
            fallbackExecutions: $this->fallbackExecutions,
            timeoutExecutions: $this->timeoutExecutions,
            circuitBreakerTrips: $this->circuitBreakerTrips + 1,
            totalExecutionTime: $this->totalExecutionTime,
            averageExecutionTime: $this->averageExecutionTime,
            maxExecutionTime: $this->maxExecutionTime,
            minExecutionTime: $this->minExecutionTime,
            lastSuccessTime: $this->lastSuccessTime,
            lastFailureTime: $this->lastFailureTime,
            lastFallbackTime: $this->lastFallbackTime,
            createdAt: $this->createdAt ?? Timestamp::now(),
            updatedAt: Timestamp::now(),
        );
    }

    /**
     * Convert to array for serialization
     */
    public function toArray(): array
    {
        return [
            'boundary_name' => $this->boundaryName,
            'total_executions' => $this->totalExecutions,
            'successful_executions' => $this->successfulExecutions,
            'failed_executions' => $this->failedExecutions,
            'fallback_executions' => $this->fallbackExecutions,
            'timeout_executions' => $this->timeoutExecutions,
            'circuit_breaker_trips' => $this->circuitBreakerTrips,
            'success_rate' => $this->getSuccessRate()->toFloat(),
            'failure_rate' => $this->getFailureRate()->toFloat(),
            'fallback_rate' => $this->getFallbackRate()->toFloat(),
            'timeout_rate' => $this->getTimeoutRate()->toFloat(),
            'total_execution_time_ms' => $this->totalExecutionTime->toMilliseconds(),
            'average_execution_time_ms' => $this->averageExecutionTime->toMilliseconds(),
            'max_execution_time_ms' => $this->maxExecutionTime->toMilliseconds(),
            'min_execution_time_ms' => $this->minExecutionTime->toMilliseconds(),
            'last_success_time' => $this->lastSuccessTime?->toIsoString(),
            'last_failure_time' => $this->lastFailureTime?->toIsoString(),
            'last_fallback_time' => $this->lastFallbackTime?->toIsoString(),
            'health_status' => $this->getHealthStatus()->value,
            'is_healthy' => $this->isHealthy(),
            'has_frequent_circuit_trips' => $this->hasFrequentCircuitBreakerTrips(),
            'created_at' => $this->createdAt?->toIsoString(),
            'updated_at' => $this->updatedAt?->toIsoString(),
        ];
    }
}
