<?php

declare(strict_types=1);

namespace Tests\Unit\Framework\Webhook;

use App\Framework\Webhook\ValueObjects\WebhookPayload;
use App\Framework\Webhook\ValueObjects\WebhookProvider;
use App\Framework\Webhook\ValueObjects\WebhookSignature;

describe('WebhookService', function () {
    describe('WebhookPayload', function () {
        it('creates webhook payload from data', function () {
            $payload = WebhookPayload::create([
                'event' => 'user.created',
                'user_id' => 123,
                'email' => 'test@example.com'
            ], 'user.created');

            expect($payload->get('user_id'))->toBe(123);
            expect($payload->get('email'))->toBe('test@example.com');
            expect($payload->getEventType())->toBe('user.created');
            expect($payload->isEmpty())->toBeFalse();
            expect($payload->has('user_id'))->toBeTrue();
            expect($payload->has('missing'))->toBeFalse();
        });

        it('creates webhook payload from request', function () {
            $data = ['event' => 'payment.succeeded', 'amount' => 1000];
            $rawBody = json_encode($data);
            $headers = [
                'X-Event-Type' => 'payment.succeeded',
                'X-Webhook-ID' => 'wh_test_123'
            ];

            $payload = WebhookPayload::fromRequest($data, $rawBody, $headers);

            expect($payload->getEventType())->toBe('payment.succeeded');
            expect($payload->getWebhookId())->toBe('wh_test_123');
            expect($payload->get('amount'))->toBe(1000);
            expect($payload->getHeader('X-Event-Type'))->toBe('payment.succeeded');
        });

        it('gets default values for missing keys', function () {
            $payload = WebhookPayload::create(['key' => 'value']);

            expect($payload->get('missing', 'default'))->toBe('default');
            expect($payload->get('key', 'default'))->toBe('value');
        });

        it('converts to array and json', function () {
            $data = ['event' => 'test', 'data' => 'value'];
            $payload = WebhookPayload::create($data);

            expect($payload->toArray())->toBe($data);
            expect($payload->jsonSerialize())->toBe($data);
        });

        it('detects empty payloads', function () {
            $empty = WebhookPayload::create([]);
            $filled = WebhookPayload::create(['key' => 'value']);

            expect($empty->isEmpty())->toBeTrue();
            expect($filled->isEmpty())->toBeFalse();
        });

        it('gets headers with case variations', function () {
            $headers = [
                'X-Event-Type' => 'test.event',
                'x-custom-header' => 'value'  // Lowercase key
            ];

            $payload = WebhookPayload::fromRequest([], '{}', $headers);

            // Exact match
            expect($payload->getHeader('X-Event-Type'))->toBe('test.event');
            // Case-insensitive fallback for lowercase keys
            expect($payload->getHeader('X-Custom-Header'))->toBe('value');
            expect($payload->getHeader('missing'))->toBeNull();
        });
    });

    describe('WebhookProvider', function () {
        it('creates stripe provider', function () {
            $stripe = WebhookProvider::stripe();

            expect($stripe->toString())->toBe('stripe');
            expect($stripe->name)->toBe('stripe');
            expect($stripe->signatureHeader)->toBe('Stripe-Signature');
            expect($stripe->eventTypeHeader)->toBe('Stripe-Event');
            expect($stripe->signatureAlgorithm)->toBe('sha256');
            expect($stripe->isKnownProvider())->toBeTrue();
        });

        it('creates github provider', function () {
            $github = WebhookProvider::github();

            expect($github->toString())->toBe('github');
            expect($github->name)->toBe('github');
            expect($github->signatureHeader)->toBe('X-Hub-Signature-256');
            expect($github->eventTypeHeader)->toBe('X-GitHub-Event');
            expect($github->signatureAlgorithm)->toBe('sha256');
            expect($github->isKnownProvider())->toBeTrue();
        });

        it('creates legal service provider', function () {
            $legal = WebhookProvider::legalService();

            expect($legal->toString())->toBe('legal-service');
            expect($legal->name)->toBe('legal-service');
            expect($legal->signatureHeader)->toBe('X-Legal-Signature');
            expect($legal->eventTypeHeader)->toBe('X-Legal-Event-Type');
            expect($legal->isKnownProvider())->toBeTrue();
        });

        it('creates generic provider', function () {
            $generic = WebhookProvider::generic('custom-service');

            expect($generic->toString())->toBe('custom-service');
            expect($generic->name)->toBe('custom-service');
            expect($generic->signatureHeader)->toBe('X-Signature');
            expect($generic->eventTypeHeader)->toBe('X-Event-Type');
            expect($generic->signatureAlgorithm)->toBe('sha256');
            expect($generic->isKnownProvider())->toBeFalse();
        });

        it('creates provider from string', function () {
            $stripe = WebhookProvider::fromString('stripe');
            expect($stripe->toString())->toBe('stripe');
            expect($stripe->signatureHeader)->toBe('Stripe-Signature');

            $github = WebhookProvider::fromString('github');
            expect($github->toString())->toBe('github');
            expect($github->signatureHeader)->toBe('X-Hub-Signature-256');

            $custom = WebhookProvider::fromString('my-service');
            expect($custom->toString())->toBe('my-service');
            expect($custom->signatureHeader)->toBe('X-Signature');
        });

        it('compares providers for equality', function () {
            $stripe1 = WebhookProvider::stripe();
            $stripe2 = WebhookProvider::fromString('stripe');
            $github = WebhookProvider::github();

            expect($stripe1->equals($stripe2))->toBeTrue();
            expect($stripe1->equals($github))->toBeFalse();
        });

        it('validates provider names', function () {
            expect(fn() => WebhookProvider::create(''))
                ->toThrow(\InvalidArgumentException::class, 'Provider name cannot be empty');
        });

        it('normalizes provider names', function () {
            $provider = WebhookProvider::create('  Custom-Service  ');

            expect($provider->toString())->toBe('custom-service');
        });
    });

    describe('WebhookSignature', function () {
        it('creates signature with algorithm', function () {
            $signature = WebhookSignature::create('abc123def456', 'sha256', 'hex');

            expect($signature->toString())->toBe('abc123def456');
            expect($signature->value)->toBe('abc123def456');
            expect($signature->algorithm)->toBe('sha256');
            expect($signature->encoding)->toBe('hex');
        });

        it('creates signature from header', function () {
            $sig1 = WebhookSignature::fromHeader('sha256=abc123');
            expect($sig1->toString())->toBe('abc123');
            expect($sig1->algorithm)->toBe('sha256');

            $sig2 = WebhookSignature::fromHeader('v1=def456');
            expect($sig2->toString())->toBe('def456');
            expect($sig2->algorithm)->toBe('v1');

            $sig3 = WebhookSignature::fromHeader('xyz789');
            expect($sig3->toString())->toBe('xyz789');
            expect($sig3->algorithm)->toBe('sha256'); // Default
        });

        it('creates signature from stripe header', function () {
            $stripeHeader = 't=1492774577,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd';

            $signature = WebhookSignature::fromStripeHeader($stripeHeader);

            expect($signature->toString())->toBe('5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd');
            expect($signature->algorithm)->toBe('sha256');
        });

        it('throws on invalid stripe header', function () {
            // Stripe header requires "key=value" format with v1 signature
            expect(fn() => WebhookSignature::fromStripeHeader('invalid'))
                ->toThrow(\InvalidArgumentException::class);
        });

        it('formats signature for header', function () {
            $signature = WebhookSignature::create('abc123', 'sha256');

            expect($signature->toHeaderFormat())->toBe('sha256=abc123');
            expect($signature->toHeaderFormat('v1'))->toBe('v1=abc123');
        });

        it('compares signatures securely', function () {
            $sig1 = WebhookSignature::create('abc123', 'sha256');
            $sig2 = WebhookSignature::create('abc123', 'sha256');
            $sig3 = WebhookSignature::create('def456', 'sha256');
            $sig4 = WebhookSignature::create('abc123', 'sha512');

            expect($sig1->equals($sig2))->toBeTrue();
            expect($sig1->equals($sig3))->toBeFalse();
            expect($sig1->equals($sig4))->toBeFalse(); // Different algorithm
        });

        it('verifies signature with sha1', function () {
            $payload = 'test payload';
            $secret = 'secret_key';
            $expectedSig = hash_hmac('sha1', $payload, $secret);

            $signature = WebhookSignature::create($expectedSig, 'sha1');

            expect($signature->verify($payload, $secret))->toBeTrue();
        });

        it('verifies signature with sha256', function () {
            $payload = 'test payload';
            $secret = 'secret_key';
            $expectedSig = hash_hmac('sha256', $payload, $secret);

            $signature = WebhookSignature::create($expectedSig, 'sha256');

            expect($signature->verify($payload, $secret))->toBeTrue();
        });

        it('verifies signature with sha512', function () {
            $payload = 'test payload';
            $secret = 'secret_key';
            $expectedSig = hash_hmac('sha512', $payload, $secret);

            $signature = WebhookSignature::create($expectedSig, 'sha512');

            expect($signature->verify($payload, $secret))->toBeTrue();
        });

        it('fails verification with wrong signature', function () {
            $signature = WebhookSignature::create('wrong_signature', 'sha256');

            expect($signature->verify('test payload', 'secret_key'))->toBeFalse();
        });

        it('fails verification with wrong secret', function () {
            $payload = 'test payload';
            $correctSecret = 'correct_secret';
            $wrongSecret = 'wrong_secret';

            $expectedSig = hash_hmac('sha256', $payload, $correctSecret);
            $signature = WebhookSignature::create($expectedSig, 'sha256');

            expect($signature->verify($payload, $wrongSecret))->toBeFalse();
        });

        it('throws on unsupported algorithm', function () {
            $signature = WebhookSignature::create('abc123', 'md5');

            expect(fn() => $signature->verify('payload', 'secret'))
                ->toThrow(\InvalidArgumentException::class, 'Unsupported algorithm: md5');
        });

        it('validates signature value', function () {
            expect(fn() => WebhookSignature::create('', 'sha256'))
                ->toThrow(\InvalidArgumentException::class, 'Webhook signature cannot be empty');
        });
    });
});
