<?php

declare(strict_types=1);

namespace App\Framework\Database\Exception;

use App\Framework\Database\ValueObjects\SqlState;
use App\Framework\Exception\Core\DatabaseErrorCode;
use App\Framework\Exception\ExceptionContext;

/**
 * Pool Exhausted Exception
 *
 * Thrown when the connection pool has reached its maximum capacity
 * and no connections are available.
 *
 * This is typically a recoverable error - the application should retry
 * after a brief delay or implement connection pooling strategies.
 */
final class PoolExhaustedException extends DatabaseException
{
    /**
     * Create exception when pool is exhausted
     *
     * @param int $currentConnections Current number of connections
     * @param int $maxConnections Maximum allowed connections
     * @param int $connectionsInUse Number of connections currently in use
     */
    public static function noConnectionsAvailable(
        int $currentConnections,
        int $maxConnections,
        int $connectionsInUse
    ): self {
        $message = "Connection pool exhausted: {$currentConnections}/{$maxConnections} connections in pool, " .
                   "{$connectionsInUse} in use, no healthy connections available";

        $context = ExceptionContext::forOperation('connection_pool.acquire', 'ConnectionPool')
            ->withData([
                'current_connections' => $currentConnections,
                'max_connections' => $maxConnections,
                'connections_in_use' => $connectionsInUse,
                'free_connections' => $currentConnections - $connectionsInUse,
                'utilization_percent' => round(($currentConnections / $maxConnections) * 100, 2),
            ]);

        // Use SQLSTATE HY000 (general driver error) for pool exhaustion
        return self::fromContext($message, $context, DatabaseErrorCode::POOL_EXHAUSTED, null, 500, new SqlState('HY000'));
    }

    /**
     * Create exception when max connections reached but all are unhealthy
     *
     * @param int $maxConnections Maximum allowed connections
     * @param int $unhealthyConnections Number of unhealthy connections
     */
    public static function allConnectionsUnhealthy(
        int $maxConnections,
        int $unhealthyConnections
    ): self {
        $message = "Connection pool exhausted: {$maxConnections} max connections reached, " .
                   "but all {$unhealthyConnections} connections are unhealthy";

        $context = ExceptionContext::forOperation('connection_pool.acquire', 'ConnectionPool')
            ->withData([
                'max_connections' => $maxConnections,
                'unhealthy_connections' => $unhealthyConnections,
                'health_status' => 'critical',
            ]);

        return self::fromContext($message, $context, DatabaseErrorCode::POOL_EXHAUSTED, null, 500, new SqlState('HY000'));
    }

    /**
     * Create exception when waiting for connection times out
     *
     * @param int $waitTimeSeconds Time spent waiting
     * @param int $currentConnections Current pool size
     * @param int $maxConnections Maximum pool size
     */
    public static function waitTimeout(
        int $waitTimeSeconds,
        int $currentConnections,
        int $maxConnections
    ): self {
        $message = "Connection pool wait timeout after {$waitTimeSeconds} seconds - " .
                   "no connections became available";

        $context = ExceptionContext::forOperation('connection_pool.wait', 'ConnectionPool')
            ->withData([
                'wait_time_seconds' => $waitTimeSeconds,
                'current_connections' => $currentConnections,
                'max_connections' => $maxConnections,
            ]);

        return self::fromContext($message, $context, DatabaseErrorCode::TIMEOUT, null, 500, new SqlState('HY000'));
    }

    /**
     * Create exception when pool cannot create new connections
     *
     * @param int $currentConnections Current pool size
     * @param int $maxConnections Maximum pool size
     * @param string|null $reason Optional reason why connection creation failed
     * @param \Throwable|null $previous Previous exception
     */
    public static function cannotCreateConnection(
        int $currentConnections,
        int $maxConnections,
        ?string $reason = null,
        ?\Throwable $previous = null
    ): self {
        $message = "Connection pool cannot create new connections";

        if ($reason !== null) {
            $message .= ": {$reason}";
        }

        $context = ExceptionContext::forOperation('connection_pool.create', 'ConnectionPool')
            ->withData([
                'current_connections' => $currentConnections,
                'max_connections' => $maxConnections,
            ]);

        if ($reason !== null) {
            $context = $context->withDebug(['failure_reason' => $reason]);
        }

        return self::fromContext($message, $context, DatabaseErrorCode::POOL_EXHAUSTED, $previous, 500, new SqlState('HY000'));
    }

    /**
     * Create exception with retry recommendation
     *
     * @param int $currentConnections Current pool size
     * @param int $maxConnections Maximum pool size
     * @param int $recommendedRetryDelayMs Recommended retry delay in milliseconds
     */
    public static function withRetryRecommendation(
        int $currentConnections,
        int $maxConnections,
        int $recommendedRetryDelayMs
    ): self {
        $message = "Connection pool temporarily exhausted - retry after {$recommendedRetryDelayMs}ms";

        $context = ExceptionContext::forOperation('connection_pool.acquire', 'ConnectionPool')
            ->withData([
                'current_connections' => $currentConnections,
                'max_connections' => $maxConnections,
                'recommended_retry_delay_ms' => $recommendedRetryDelayMs,
            ]);

        $exception = self::fromContext($message, $context, DatabaseErrorCode::POOL_EXHAUSTED, null, 500, new SqlState('HY000'));

        // Set retry after in seconds
        return $exception->withRetryAfter((int) ceil($recommendedRetryDelayMs / 1000));
    }

    /**
     * Get recommended action for pool exhaustion
     *
     * @return string Recommended action
     */
    public function getRecommendedAction(): string
    {
        $data = $this->context->data;

        if (isset($data['utilization_percent']) && $data['utilization_percent'] >= 95) {
            return 'Increase max_connections pool size or optimize connection usage';
        }

        if (isset($data['unhealthy_connections']) && $data['unhealthy_connections'] > 0) {
            return 'Check database server health - connections are failing health checks';
        }

        if (isset($data['recommended_retry_delay_ms'])) {
            return "Retry after {$data['recommended_retry_delay_ms']}ms delay";
        }

        return 'Retry operation or implement connection pooling strategies';
    }
}
