<?php

declare(strict_types=1);

require_once __DIR__ . '/../../vendor/autoload.php';

use App\Framework\LiveComponents\ValueObjects\EventPayload;

echo "Testing EventPayload Value Object\n";
echo "==================================\n\n";

$testsPassed = 0;
$testsFailed = 0;

function test(string $name, callable $fn): void
{
    global $testsPassed, $testsFailed;

    try {
        $fn();
        echo "✅ {$name}\n";
        $testsPassed++;
    } catch (Throwable $e) {
        echo "❌ {$name}\n";
        echo "   Error: {$e->getMessage()}\n";
        $testsFailed++;
    }
}

// Test 1: Create from array
test('Create from array', function () {
    $payload = EventPayload::fromArray(['key' => 'value', 'number' => 42]);
    assert($payload->get('key') === 'value');
    assert($payload->get('number') === 42);
});

// Test 2: Create empty
test('Create empty', function () {
    $payload = EventPayload::empty();
    assert($payload->isEmpty() === true);
    assert($payload->size() === 0);
});

// Test 3: Get with default
test('Get with default', function () {
    $payload = EventPayload::fromArray(['key' => 'value']);
    assert($payload->get('missing', 'default') === 'default');
});

// Test 4: Has key
test('Has key', function () {
    $payload = EventPayload::fromArray(['key' => 'value']);
    assert($payload->has('key') === true);
    assert($payload->has('missing') === false);
});

// Test 5: Get string
test('Get string', function () {
    $payload = EventPayload::fromArray(['name' => 'John', 'age' => 30]);
    assert($payload->getString('name') === 'John');
    assert($payload->getString('age') === '30'); // Type coercion
});

// Test 6: Require string
test('Require string', function () {
    $payload = EventPayload::fromArray(['name' => 'John']);
    assert($payload->requireString('name') === 'John');
});

// Test 7: Require string throws on missing
test('Require string throws on missing', function () {
    $payload = EventPayload::empty();

    try {
        $payload->requireString('missing');
        assert(false, 'Should have thrown exception');
    } catch (InvalidArgumentException $e) {
        assert(str_contains($e->getMessage(), 'missing'));
    }
});

// Test 8: Get int
test('Get int', function () {
    $payload = EventPayload::fromArray(['count' => 42, 'string_num' => '100']);
    assert($payload->getInt('count') === 42);
    assert($payload->getInt('string_num') === 100); // Type coercion
});

// Test 9: Get float
test('Get float', function () {
    $payload = EventPayload::fromArray(['price' => 19.99, 'integer' => 42]);
    assert($payload->getFloat('price') === 19.99);
    assert($payload->getFloat('integer') === 42.0); // Type coercion
});

// Test 10: Get bool
test('Get bool', function () {
    $payload = EventPayload::fromArray(['active' => true, 'inactive' => false]);
    assert($payload->getBool('active') === true);
    assert($payload->getBool('inactive') === false);
});

// Test 11: Get array
test('Get array', function () {
    $payload = EventPayload::fromArray(['items' => ['a', 'b', 'c']]);
    assert($payload->getArray('items') === ['a', 'b', 'c']);
});

// Test 12: With (immutable update)
test('With (immutable update)', function () {
    $payload1 = EventPayload::fromArray(['key' => 'value']);
    $payload2 = $payload1->with('new', 'data');

    assert($payload1->has('new') === false); // Original unchanged
    assert($payload2->has('new') === true);
    assert($payload2->get('key') === 'value'); // Old data preserved
});

// Test 13: WithMany
test('WithMany', function () {
    $payload1 = EventPayload::fromArray(['a' => 1]);
    $payload2 = $payload1->withMany(['b' => 2, 'c' => 3]);

    assert($payload2->get('a') === 1);
    assert($payload2->get('b') === 2);
    assert($payload2->get('c') === 3);
});

// Test 14: Without
test('Without', function () {
    $payload1 = EventPayload::fromArray(['a' => 1, 'b' => 2, 'c' => 3]);
    $payload2 = $payload1->without('b');

    assert($payload1->has('b') === true); // Original unchanged
    assert($payload2->has('b') === false);
    assert($payload2->get('a') === 1);
});

// Test 15: Only
test('Only', function () {
    $payload1 = EventPayload::fromArray(['a' => 1, 'b' => 2, 'c' => 3]);
    $payload2 = $payload1->only(['a', 'c']);

    assert($payload2->size() === 2);
    assert($payload2->has('a') === true);
    assert($payload2->has('b') === false);
    assert($payload2->has('c') === true);
});

// Test 16: Except
test('Except', function () {
    $payload1 = EventPayload::fromArray(['a' => 1, 'b' => 2, 'c' => 3]);
    $payload2 = $payload1->except(['b']);

    assert($payload2->size() === 2);
    assert($payload2->has('a') === true);
    assert($payload2->has('b') === false);
    assert($payload2->has('c') === true);
});

// Test 17: Merge
test('Merge', function () {
    $payload1 = EventPayload::fromArray(['a' => 1, 'b' => 2]);
    $payload2 = EventPayload::fromArray(['c' => 3, 'd' => 4]);
    $merged = $payload1->merge($payload2);

    assert($merged->size() === 4);
    assert($merged->get('a') === 1);
    assert($merged->get('d') === 4);
});

// Test 18: Equals
test('Equals', function () {
    $payload1 = EventPayload::fromArray(['a' => 1, 'b' => 2]);
    $payload2 = EventPayload::fromArray(['a' => 1, 'b' => 2]);
    $payload3 = EventPayload::fromArray(['a' => 1, 'b' => 3]);

    assert($payload1->equals($payload2) === true);
    assert($payload1->equals($payload3) === false);
});

// Test 19: Keys
test('Keys', function () {
    $payload = EventPayload::fromArray(['a' => 1, 'b' => 2, 'c' => 3]);
    $keys = $payload->keys();

    assert(count($keys) === 3);
    assert(in_array('a', $keys));
    assert(in_array('b', $keys));
    assert(in_array('c', $keys));
});

// Test 20: ToArray
test('ToArray', function () {
    $data = ['key' => 'value', 'number' => 42];
    $payload = EventPayload::fromArray($data);

    assert($payload->toArray() === $data);
});

// Test 21: Validation - non-string keys
test('Validation rejects non-string keys', function () {
    try {
        EventPayload::fromArray([0 => 'value', 'key' => 'data']);
        assert(false, 'Should have thrown exception');
    } catch (InvalidArgumentException $e) {
        assert(str_contains($e->getMessage(), 'must be strings'));
    }
});

echo "\n";
echo "==================================\n";
echo "Tests passed: {$testsPassed}\n";
echo "Tests failed: {$testsFailed}\n";
echo "==================================\n";

if ($testsFailed > 0) {
    exit(1);
}

echo "\n✅ All EventPayload tests passed!\n";
