<?php

declare(strict_types=1);

namespace App\Framework\Security\RequestSigning;

use App\Framework\Http\Request;

/**
 * Verifies HTTP request signatures
 */
final readonly class RequestVerifier
{
    public function __construct(
        private SigningKeyRepository $keyRepository,
    ) {
    }

    /**
     * Verify a request signature
     */
    public function verify(Request $request): VerificationResult
    {
        try {
            // Extract signature from header
            $signatureHeader = $request->headers->getFirst('Signature');
            if ($signatureHeader === null) {
                return VerificationResult::failure('Missing Signature header');
            }

            $signature = RequestSignature::fromHttpSignatureHeader($signatureHeader);

            // Check if signature is expired
            if ($signature->isExpired()) {
                return VerificationResult::failure('Signature has expired');
            }

            // Get signing key
            $key = $this->keyRepository->findByKeyId($signature->keyId);
            if ($key === null) {
                return VerificationResult::failure('Unknown key ID: ' . $signature->keyId);
            }

            if (! $key->isValid()) {
                return VerificationResult::failure('Signing key is not valid');
            }

            // Verify algorithm matches
            if ($key->algorithm !== $signature->algorithm) {
                return VerificationResult::failure('Algorithm mismatch');
            }

            // Build signing string
            $signingString = new SigningString($request);
            $stringToSign = $signingString->build($signature->headers);

            // Verify signature
            $isValid = $this->verifySignature($stringToSign, $signature->signature, $key);

            if ($isValid) {
                return VerificationResult::success($signature, $key);
            } else {
                return VerificationResult::failure('Invalid signature');
            }

        } catch (\Exception $e) {
            return VerificationResult::failure('Signature verification error: ' . $e->getMessage());
        }
    }

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

    /**
     * Verify HMAC signature
     */
    private function verifyHmacSignature(string $data, string $signature, SigningKey $key, string $algorithm): bool
    {
        $expectedSignature = hash_hmac($algorithm, $data, $key->keyMaterial, true);
        $expectedSignatureBase64 = base64_encode($expectedSignature);

        return hash_equals($expectedSignatureBase64, $signature);
    }

    /**
     * Verify RSA signature
     */
    private function verifyRsaSignature(string $data, string $signature, SigningKey $key): bool
    {
        // Extract public key from private key
        $privateKey = openssl_pkey_get_private($key->keyMaterial);
        if ($privateKey === false) {
            return false;
        }

        $keyDetails = openssl_pkey_get_details($privateKey);
        if ($keyDetails === false || ! isset($keyDetails['key'])) {
            return false;
        }

        $publicKey = openssl_pkey_get_public($keyDetails['key']);
        if ($publicKey === false) {
            return false;
        }

        $signatureBinary = base64_decode($signature);
        if ($signatureBinary === false) {
            return false;
        }

        $result = openssl_verify($data, $signatureBinary, $publicKey, $key->algorithm->getOpenSSLSignatureAlgorithm());

        return $result === 1;
    }

    /**
     * Verify digest header if present
     */
    public function verifyDigest(Request $request): bool
    {
        $digestHeader = $request->headers->getFirst('Digest');
        if ($digestHeader === null) {
            return true; // No digest to verify
        }

        $body = $request->body;

        // Parse digest header: "SHA256=base64hash"
        if (! preg_match('/^([A-Z0-9]+)=(.+)$/', $digestHeader, $matches)) {
            return false;
        }

        $algorithm = strtolower($matches[1]);
        $expectedHash = $matches[2];

        $actualHash = base64_encode(hash($algorithm, $body, true));

        return hash_equals($expectedHash, $actualHash);
    }
}
