<?php
declare(strict_types=1);
namespace App\Security;

use RuntimeException;

final class ApiCommunicationSecurity
{
    public static function correlationId(): string
    {
        $incoming = trim((string)($_SERVER['HTTP_X_CORRELATION_ID'] ?? ''));
        if ($incoming !== '' && preg_match('/^[A-Za-z0-9._:-]{8,96}$/', $incoming)) return $incoming;
        return bin2hex(random_bytes(16));
    }

    /** @param array<string,mixed> $headers */
    public static function verifySignedWrite(string $method, string $apiCode, string $rawBody, string $token, array $headers): void
    {
        if (!in_array(strtoupper($method), ['POST','PUT','PATCH','DELETE'], true)) return;
        $timestamp = trim((string)($headers['X-Axiom-Timestamp'] ?? $headers['x-axiom-timestamp'] ?? ''));
        $nonce = trim((string)($headers['X-Axiom-Nonce'] ?? $headers['x-axiom-nonce'] ?? ''));
        $signature = trim((string)($headers['X-Axiom-Signature'] ?? $headers['x-axiom-signature'] ?? ''));
        if (!ctype_digit($timestamp) || abs(time() - (int)$timestamp) > self::intEnv('API_SIGNATURE_MAX_SKEW_SECONDS', 300, 30, 900)) {
            throw new RuntimeException('Horodatage de signature absent ou expiré.');
        }
        if (!preg_match('/^[A-Za-z0-9_-]{16,96}$/', $nonce)) throw new RuntimeException('Nonce API invalide.');
        if (!preg_match('/^[a-f0-9]{64}$/i', $signature)) throw new RuntimeException('Signature API invalide.');
        self::consumeNonce($apiCode, $nonce, (int)$timestamp);
        $canonical = strtoupper($method) . "\n" . $apiCode . "\n" . $timestamp . "\n" . $nonce . "\n" . hash('sha256', $rawBody);
        $expected = hash_hmac('sha256', $canonical, hash('sha256', $token, true));
        if (!hash_equals($expected, strtolower($signature))) throw new RuntimeException('Signature API non reconnue.');
    }

    private static function consumeNonce(string $apiCode, string $nonce, int $timestamp): void
    {
        $dir = BASE_PATH . '/storage/cache/api-nonces';
        if (!is_dir($dir) && !mkdir($dir, 0770, true) && !is_dir($dir)) throw new RuntimeException('Stockage anti-rejeu indisponible.');
        $key = hash('sha256', $apiCode . '|' . $nonce);
        $path = $dir . '/' . $key . '.nonce';
        $handle = @fopen($path, 'x');
        if ($handle === false) throw new RuntimeException('Requête API déjà utilisée.');
        fwrite($handle, (string)$timestamp); fclose($handle); @chmod($path, 0600);
        foreach (glob($dir . '/*.nonce') ?: [] as $file) {
            if (@filemtime($file) !== false && @filemtime($file) < time() - 1800) @unlink($file);
        }
    }

    private static function intEnv(string $name, int $default, int $min, int $max): int
    {
        $value = filter_var($_ENV[$name] ?? getenv($name) ?: $default, FILTER_VALIDATE_INT);
        return max($min, min($max, $value === false ? $default : $value));
    }
}
