<?php

declare(strict_types=1);

namespace App\Framework\Security\RequestSigning;

use App\Framework\Http\HttpRequest;
use App\Framework\Http\Request;
use App\Framework\Http\RequestManipulator;

/**
 * Signs HTTP requests using various cryptographic algorithms
 */
final readonly class RequestSigner
{
    public function __construct(
        private RequestManipulator $requestManipulator,
    ) {
    }

    /**
     * Sign a request using the specified key and headers
     */
    public function sign(
        Request $request,
        SigningKey $key,
        ?array $headers = null,
        ?int $expires = null,
    ): RequestSignature {
        if (! $key->isValid()) {
            throw new \InvalidArgumentException('Signing key is not valid');
        }

        $headers ??= SigningString::getDefaultHeaders();
        $signingString = new SigningString($request);
        $stringToSign = $signingString->build($headers);

        $signature = $this->createSignature($stringToSign, $key);

        return new RequestSignature(
            signature: $signature,
            keyId: $key->keyId,
            algorithm: $key->algorithm,
            headers: $headers,
            timestamp: new \DateTimeImmutable(),
            expires: $expires,
        );
    }

    /**
     * Create a signature using the appropriate algorithm
     */
    private function createSignature(string $data, SigningKey $key): string
    {
        return match ($key->algorithm) {
            SigningAlgorithm::HMAC_SHA256 => $this->createHmacSignature($data, $key, 'sha256'),
            SigningAlgorithm::HMAC_SHA512 => $this->createHmacSignature($data, $key, 'sha512'),
            SigningAlgorithm::RSA_SHA256 => $this->createRsaSignature($data, $key),
            SigningAlgorithm::ED25519 => throw new \InvalidArgumentException('ED25519 not yet implemented'),
        };
    }

    /**
     * Create HMAC signature
     */
    private function createHmacSignature(string $data, SigningKey $key, string $algorithm): string
    {
        $signature = hash_hmac($algorithm, $data, $key->keyMaterial, true);

        return base64_encode($signature);
    }

    /**
     * Create RSA signature
     */
    private function createRsaSignature(string $data, SigningKey $key): string
    {
        $privateKey = openssl_pkey_get_private($key->keyMaterial);
        if ($privateKey === false) {
            throw new \RuntimeException('Failed to load RSA private key');
        }

        $signature = '';
        $result = openssl_sign($data, $signature, $privateKey, $key->algorithm->getOpenSSLSignatureAlgorithm());

        if (! $result) {
            throw new \RuntimeException('Failed to create RSA signature');
        }

        return base64_encode($signature);
    }

    /**
     * Create a digest header for request body
     */
    public function createDigest(string $body, string $algorithm = 'sha256'): string
    {
        $hash = hash($algorithm, $body, true);

        return strtoupper($algorithm) . '=' . base64_encode($hash);
    }

    /**
     * Sign a request and return new request with signature headers
     */
    public function signRequest(
        HttpRequest $request,
        SigningKey $key,
        ?array $headers = null,
        ?int $expires = null,
    ): HttpRequest {
        $modifiedRequest = $request;

        // Add digest header if request has body
        if ($request->body !== '') {
            $digest = $this->createDigest($request->body);
            $modifiedRequest = $this->requestManipulator->withHeader($modifiedRequest, 'Digest', $digest);
        }

        // Add date header if not present
        if ($modifiedRequest->headers->getFirst('Date') === null) {
            $date = gmdate('D, d M Y H:i:s T');
            $modifiedRequest = $this->requestManipulator->withHeader($modifiedRequest, 'Date', $date);
        }

        // Create signature
        $signature = $this->sign($modifiedRequest, $key, $headers, $expires);

        // Add signature header and return final request
        return $this->requestManipulator->withHeader($modifiedRequest, 'Signature', $signature->toHttpSignatureHeader());
    }
}
