<?php

declare(strict_types=1);

namespace App\Framework\Cryptography;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\DateTime\HighResolutionClock;
use App\Framework\DateTime\Timer;

/**
 * Constant Time Executor
 *
 * Executes operations in constant time to prevent timing attacks.
 * Enforces a minimum execution time by sleeping if the operation
 * completes faster than the target duration.
 *
 * Particularly important for:
 * - Password verification
 * - TOTP verification
 * - Token validation
 * - Any security-sensitive comparison operations
 */
final readonly class ConstantTimeExecutor
{
    private Duration $targetDuration;

    public function __construct(
        private HighResolutionClock $clock,
        private Timer $timer,
        ?Duration $targetDuration = null
    ) {
        $this->targetDuration = $targetDuration ?? Duration::fromMilliseconds(100);
    }

    /**
     * Create with millisecond target duration
     */
    public static function withMilliseconds(
        HighResolutionClock $clock,
        Timer $timer,
        int $milliseconds
    ): self {
        return new self($clock, $timer, Duration::fromMilliseconds($milliseconds));
    }

    /**
     * Execute operation in constant time
     */
    public function execute(callable $operation): mixed
    {
        $startTime = $this->clock->hrtime();

        try {
            $result = $operation();
        } finally {
            $this->enforceConstantTime($startTime);
        }

        return $result;
    }

    /**
     * Execute operation and return both result and actual duration
     * @return array<string, mixed>
     */
    public function executeWithTiming(callable $operation): array
    {
        $startTime = $this->clock->hrtime();
        $result = $operation();
        $operationEndTime = $this->clock->hrtime();

        $this->enforceConstantTime($startTime);
        $totalEndTime = $this->clock->hrtime();

        return [
            'result' => $result,
            'operation_duration' => $operationEndTime->subtract($startTime),
            'total_duration' => $totalEndTime->subtract($startTime),
            'sleep_duration' => $totalEndTime->subtract($operationEndTime),
        ];
    }

    /**
     * Get the configured target duration
     */
    public function getTargetDuration(): Duration
    {
        return $this->targetDuration;
    }

    /**
     * Enforce constant time by sleeping if needed
     */
    private function enforceConstantTime(Duration $startTime): void
    {
        $currentTime = $this->clock->hrtime();
        $elapsed = $currentTime->subtract($startTime);

        if ($elapsed->toNanoseconds() < $this->targetDuration->toNanoseconds()) {
            $sleepDuration = $this->targetDuration->subtract($elapsed);
            $this->timer->sleep($sleepDuration);
        }
    }
}
