<?php

declare(strict_types=1);

namespace Tests\Framework\Security\RequestSigning;

use App\Framework\Http\Headers;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Security\RequestSigning\InMemorySigningKeyRepository;
use App\Framework\Security\RequestSigning\RequestVerifier;
use App\Framework\Security\RequestSigning\SigningKey;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class RequestVerifierTest extends TestCase
{
    private RequestVerifier $verifier;

    private InMemorySigningKeyRepository $keyRepository;

    protected function setUp(): void
    {
        $this->keyRepository = new InMemorySigningKeyRepository();
        $this->verifier = new RequestVerifier($this->keyRepository);
    }

    #[Test]
    public function it_can_verify_valid_hmac_signature(): void
    {
        $key = SigningKey::createHmac('test-key', 'my-secret-key-that-is-long-enough-for-security');
        $this->keyRepository->store($key);

        // Manually create a valid signature for testing
        $request = new HttpRequest(
            method: Method::GET,
            path: '/api/test',
            headers: new Headers([
                'Host' => 'example.com',
                'Date' => 'Thu, 05 Jan 2023 21:31:40 GMT',
                'Signature' => 'keyId="test-key",algorithm="hmac-sha256",headers="(request-target) host date",signature="mock-signature"',
            ])
        );

        // We need to mock the signature creation to match what would be generated
        $signingString = "(request-target): get /api/test\nhost: example.com\ndate: Thu, 05 Jan 2023 21:31:40 GMT";
        $expectedSignature = base64_encode(hash_hmac('sha256', $signingString, 'my-secret-key-that-is-long-enough-for-security', true));

        $validRequest = new HttpRequest(
            method: Method::GET,
            path: '/api/test',
            headers: new Headers([
                'Host' => 'example.com',
                'Date' => 'Thu, 05 Jan 2023 21:31:40 GMT',
                'Signature' => "keyId=\"test-key\",algorithm=\"hmac-sha256\",headers=\"(request-target) host date\",signature=\"{$expectedSignature}\"",
            ])
        );

        $result = $this->verifier->verify($validRequest);

        $this->assertTrue($result->isSuccess());
        $this->assertEquals('test-key', $result->signature->keyId);
    }

    #[Test]
    public function it_fails_verification_for_missing_signature(): void
    {
        $request = new HttpRequest(
            method: Method::GET,
            path: '/api/test',
            headers: new Headers(['Host' => 'example.com'])
        );

        $result = $this->verifier->verify($request);

        $this->assertTrue($result->isFailure());
        $this->assertEquals('Missing Signature header', $result->errorMessage);
    }

    #[Test]
    public function it_fails_verification_for_unknown_key(): void
    {
        $request = new HttpRequest(
            method: Method::GET,
            path: '/api/test',
            headers: new Headers([
                'Host' => 'example.com',
                'Date' => 'Thu, 05 Jan 2023 21:31:40 GMT',
                'Signature' => 'keyId="unknown-key",algorithm="hmac-sha256",headers="(request-target) host date",signature="test-signature"',
            ])
        );

        $result = $this->verifier->verify($request);

        $this->assertTrue($result->isFailure());
        $this->assertEquals('Unknown key ID: unknown-key', $result->errorMessage);
    }

    #[Test]
    public function it_fails_verification_for_invalid_signature(): void
    {
        $key = SigningKey::createHmac('test-key', 'my-secret-key-that-is-long-enough-for-security');
        $this->keyRepository->store($key);

        $request = new HttpRequest(
            method: Method::GET,
            path: '/api/test',
            headers: new Headers([
                'Host' => 'example.com',
                'Date' => 'Thu, 05 Jan 2023 21:31:40 GMT',
                'Signature' => 'keyId="test-key",algorithm="hmac-sha256",headers="(request-target) host date",signature="invalid-signature"',
            ])
        );

        $result = $this->verifier->verify($request);

        $this->assertTrue($result->isFailure());
        $this->assertEquals('Invalid signature', $result->errorMessage);
    }

    #[Test]
    public function it_can_verify_digest_header(): void
    {
        $body = '{"test": "data"}';
        $expectedHash = base64_encode(hash('sha256', $body, true));

        $request = new HttpRequest(
            method: Method::POST,
            path: '/api/test',
            headers: new Headers([
                'Digest' => "SHA256={$expectedHash}",
            ]),
            body: $body
        );

        $this->assertTrue($this->verifier->verifyDigest($request));
    }

    #[Test]
    public function it_fails_digest_verification_for_wrong_hash(): void
    {
        $request = new HttpRequest(
            method: Method::POST,
            path: '/api/test',
            headers: new Headers([
                'Digest' => 'SHA256=wrong-hash',
            ]),
            body: '{"test": "data"}'
        );

        $this->assertFalse($this->verifier->verifyDigest($request));
    }

    #[Test]
    public function it_passes_digest_verification_when_no_digest_header(): void
    {
        $request = new HttpRequest(
            method: Method::POST,
            path: '/api/test',
            body: '{"test": "data"}'
        );

        $this->assertTrue($this->verifier->verifyDigest($request));
    }

    #[Test]
    public function it_fails_verification_for_expired_key(): void
    {
        $expiredKey = SigningKey::createHmac(
            'expired-key',
            'secret-that-is-long-enough-for-security',
            expiresAt: new \DateTimeImmutable('2020-01-01')
        );
        $this->keyRepository->store($expiredKey);

        $request = new HttpRequest(
            method: Method::GET,
            path: '/api/test',
            headers: new Headers([
                'Host' => 'example.com',
                'Date' => 'Thu, 05 Jan 2023 21:31:40 GMT',
                'Signature' => 'keyId="expired-key",algorithm="hmac-sha256",headers="(request-target) host date",signature="test-signature"',
            ])
        );

        $result = $this->verifier->verify($request);

        $this->assertTrue($result->isFailure());
        $this->assertEquals('Signing key is not valid', $result->errorMessage);
    }
}
