<?php

declare(strict_types=1);

/**
 * LiveComponent Security Testing Examples
 *
 * Demonstrates testing security features:
 * - CSRF protection
 * - Rate limiting
 * - Idempotency key validation
 */

use function Pest\LiveComponents\mountComponent;
use function Pest\LiveComponents\callAction;

describe('LiveComponent Security', function () {
    describe('CSRF Protection', function () {
        it('requires valid CSRF token for actions', function () {
            $component = mountComponent('counter:test', ['count' => 0]);

            // Without CSRF token should fail
            expect(fn() => callAction($component, 'increment', [
                '_csrf_token' => 'invalid_token'
            ]))->toThrow(\App\Framework\Exception\Security\CsrfTokenMismatchException::class);
        });

        it('accepts valid CSRF token', function () {
            $component = mountComponent('counter:test', ['count' => 0]);

            // Get valid CSRF token from session
            $session = container()->get(\App\Framework\Http\Session::class);
            $csrfToken = $session->getCsrfToken();

            $result = callAction($component, 'increment', [
                '_csrf_token' => $csrfToken
            ]);

            expect($result['state']['count'])->toBe(1);
        });

        it('regenerates CSRF token after action', function () {
            $component = mountComponent('counter:test', ['count' => 0]);
            $session = container()->get(\App\Framework\Http\Session::class);

            $oldToken = $session->getCsrfToken();

            callAction($component, 'increment', [
                '_csrf_token' => $oldToken
            ]);

            $newToken = $session->getCsrfToken();

            expect($newToken)->not->toBe($oldToken);
        });
    });

    describe('Rate Limiting', function () {
        it('enforces rate limits on actions', function () {
            $component = mountComponent('counter:test', ['count' => 0]);

            // Execute action multiple times rapidly
            for ($i = 0; $i < 10; $i++) {
                callAction($component, 'increment');
            }

            // 11th request should be rate limited
            expect(fn() => callAction($component, 'increment'))
                ->toThrow(\App\Framework\Exception\Http\RateLimitExceededException::class);
        });

        it('includes retry-after header in rate limit response', function () {
            $component = mountComponent('counter:test', ['count' => 0]);

            // Exhaust rate limit
            for ($i = 0; $i < 10; $i++) {
                callAction($component, 'increment');
            }

            try {
                callAction($component, 'increment');
            } catch (\App\Framework\Exception\Http\RateLimitExceededException $e) {
                expect($e->getRetryAfter())->toBeGreaterThan(0);
            }
        });

        it('resets rate limit after cooldown period', function () {
            $component = mountComponent('counter:test', ['count' => 0]);

            // Exhaust rate limit
            for ($i = 0; $i < 10; $i++) {
                callAction($component, 'increment');
            }

            // Wait for cooldown (simulate with cache clear in tests)
            $cache = container()->get(\App\Framework\Cache\Cache::class);
            $cache->clear();

            // Should work again after cooldown
            $result = callAction($component, 'increment');
            expect($result['state']['count'])->toBe(11);
        });
    });

    describe('Idempotency Keys', function () {
        it('prevents duplicate action execution with same idempotency key', function () {
            $component = mountComponent('counter:test', ['count' => 0]);
            $idempotencyKey = 'test-key-' . uniqid();

            // First execution
            $result1 = callAction($component, 'increment', [
                'idempotency_key' => $idempotencyKey
            ]);

            expect($result1['state']['count'])->toBe(1);

            // Second execution with same key should return cached result
            $result2 = callAction($result1, 'increment', [
                'idempotency_key' => $idempotencyKey
            ]);

            // Count should still be 1 (not 2) because action was not re-executed
            expect($result2['state']['count'])->toBe(1);
        });

        it('allows different actions with different idempotency keys', function () {
            $component = mountComponent('counter:test', ['count' => 0]);

            $result1 = callAction($component, 'increment', [
                'idempotency_key' => 'key-1'
            ]);

            $result2 = callAction($result1, 'increment', [
                'idempotency_key' => 'key-2'
            ]);

            expect($result2['state']['count'])->toBe(2);
        });

        it('idempotency key expires after TTL', function () {
            $component = mountComponent('counter:test', ['count' => 0]);
            $idempotencyKey = 'test-key-expiry';

            // First execution
            callAction($component, 'increment', [
                'idempotency_key' => $idempotencyKey
            ]);

            // Simulate TTL expiry by clearing cache
            $cache = container()->get(\App\Framework\Cache\Cache::class);
            $cache->clear();

            // Should execute again after expiry
            $result = callAction($component, 'increment', [
                'idempotency_key' => $idempotencyKey
            ]);

            expect($result['state']['count'])->toBe(2);
        });

        it('includes idempotency metadata in response', function () {
            $component = mountComponent('counter:test', ['count' => 0]);
            $idempotencyKey = 'test-key-metadata';

            $result = callAction($component, 'increment', [
                'idempotency_key' => $idempotencyKey
            ]);

            // Check for idempotency metadata (if implemented)
            // This depends on your specific implementation
            expect($result)->toHaveKey('idempotency');
            expect($result['idempotency']['key'])->toBe($idempotencyKey);
            expect($result['idempotency']['cached'])->toBeFalse();
        });
    });

    describe('Combined Security Features', function () {
        it('enforces all security layers together', function () {
            $component = mountComponent('counter:test', ['count' => 0]);
            $session = container()->get(\App\Framework\Http\Session::class);
            $csrfToken = $session->getCsrfToken();
            $idempotencyKey = 'combined-test-' . uniqid();

            // Valid request with all security features
            $result = callAction($component, 'increment', [
                '_csrf_token' => $csrfToken,
                'idempotency_key' => $idempotencyKey
            ]);

            expect($result['state']['count'])->toBe(1);

            // Retry with same idempotency key but new CSRF token
            $newCsrfToken = $session->getCsrfToken();

            $result2 = callAction($result, 'increment', [
                '_csrf_token' => $newCsrfToken,
                'idempotency_key' => $idempotencyKey
            ]);

            // Should return cached result due to idempotency
            expect($result2['state']['count'])->toBe(1);
        });

        it('validates security in correct order: CSRF -> Rate Limit -> Idempotency', function () {
            $component = mountComponent('counter:test', ['count' => 0]);

            // Invalid CSRF should fail before rate limit check
            try {
                callAction($component, 'increment', [
                    '_csrf_token' => 'invalid'
                ]);
                expect(false)->toBeTrue('Should have thrown CSRF exception');
            } catch (\Exception $e) {
                expect($e)->toBeInstanceOf(\App\Framework\Exception\Security\CsrfTokenMismatchException::class);
            }

            // Rate limit should be checked before idempotency
            $session = container()->get(\App\Framework\Http\Session::class);
            $csrfToken = $session->getCsrfToken();

            // Exhaust rate limit
            for ($i = 0; $i < 10; $i++) {
                callAction($component, 'increment', [
                    '_csrf_token' => $session->getCsrfToken()
                ]);
            }

            // Even with valid idempotency key, rate limit should trigger first
            try {
                callAction($component, 'increment', [
                    '_csrf_token' => $session->getCsrfToken(),
                    'idempotency_key' => 'test-key'
                ]);
                expect(false)->toBeTrue('Should have thrown rate limit exception');
            } catch (\Exception $e) {
                expect($e)->toBeInstanceOf(\App\Framework\Exception\Http\RateLimitExceededException::class);
            }
        });
    });
});
