<?php

declare(strict_types=1);

require_once __DIR__ . '/../../src/Framework/Database/Locks/LockKey.php';
require_once __DIR__ . '/../../src/Framework/Database/Locks/AdvisoryLockService.php';

use App\Framework\Database\Locks\LockKey;
use App\Framework\Database\Locks\AdvisoryLockService;

echo "Testing PostgreSQL Advisory Locks\n";
echo "==================================\n\n";

try {
    // Create two separate PDO connections to simulate concurrent access
    $pdo1 = new PDO(
        'pgsql:host=db;dbname=michaelschiemer',
        'postgres',
        'StartSimple2024!'
    );
    $pdo1->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $pdo2 = new PDO(
        'pgsql:host=db;dbname=michaelschiemer',
        'postgres',
        'StartSimple2024!'
    );
    $pdo2->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    echo "✅ Two database connections established\n\n";

    $lockService1 = new AdvisoryLockService($pdo1);
    $lockService2 = new AdvisoryLockService($pdo2);

    // Test 1: Basic lock acquisition and release
    echo "Test 1: Basic Lock Acquisition and Release\n";
    echo "===========================================\n";

    $key1 = LockKey::fromString('test-resource-1');
    echo "Lock key generated: {$key1->toInt()}\n";

    // Connection 1 acquires lock
    $acquired = $lockService1->tryLock($key1);
    echo $acquired ? "✅ Connection 1: Lock acquired\n" : "❌ Connection 1: Failed to acquire lock\n";

    // Connection 2 tries to acquire same lock (should fail)
    $acquired2 = $lockService2->tryLock($key1);
    echo !$acquired2 ? "✅ Connection 2: Lock correctly blocked\n" : "❌ Connection 2: Should not have acquired lock\n";

    // Connection 1 releases lock
    $released = $lockService1->unlock($key1);
    echo $released ? "✅ Connection 1: Lock released\n" : "❌ Connection 1: Failed to release lock\n";

    // Connection 2 tries again (should succeed now)
    $acquired2 = $lockService2->tryLock($key1);
    echo $acquired2 ? "✅ Connection 2: Lock acquired after release\n" : "❌ Connection 2: Failed to acquire lock\n";

    $lockService2->unlock($key1);
    echo "\n";

    // Test 2: Blocking lock behavior
    echo "Test 2: Non-Blocking vs Blocking Locks\n";
    echo "=======================================\n";

    $key2 = LockKey::fromInt(12345);

    // Connection 1 acquires lock
    $lockService1->tryLock($key2);
    echo "✅ Connection 1: Lock acquired (non-blocking)\n";

    // Connection 2 tries non-blocking (should fail immediately)
    $start = microtime(true);
    $acquired2 = $lockService2->tryLock($key2);
    $duration = microtime(true) - $start;

    echo !$acquired2 ? "✅ Connection 2: tryLock() failed immediately (took " . round($duration * 1000, 2) . "ms)\n" : "❌ Should not acquire lock\n";

    $lockService1->unlock($key2);
    echo "✅ Connection 1: Lock released\n\n";

    // Test 3: Transaction-scoped locks
    echo "Test 3: Transaction-Scoped Locks (Auto-Release)\n";
    echo "================================================\n";

    $key3 = LockKey::fromString('transaction-lock');

    // Connection 1 begins transaction and acquires transaction lock
    $pdo1->beginTransaction();
    $lockService1->lockTransaction($key3);
    echo "✅ Connection 1: Transaction lock acquired\n";

    // Connection 2 tries to acquire (should fail)
    $acquired2 = $lockService2->tryLockTransaction($key3);
    echo !$acquired2 ? "✅ Connection 2: Transaction lock correctly blocked\n" : "❌ Should not acquire lock\n";

    // Connection 1 commits (auto-releases lock)
    $pdo1->commit();
    echo "✅ Connection 1: Transaction committed (lock auto-released)\n";

    // Connection 2 tries again (should succeed now)
    $pdo2->beginTransaction();
    $acquired2 = $lockService2->tryLockTransaction($key3);
    echo $acquired2 ? "✅ Connection 2: Transaction lock acquired after auto-release\n" : "❌ Failed to acquire lock\n";
    $pdo2->commit();
    echo "\n";

    // Test 4: withLock helper method
    echo "Test 4: withLock() Helper Method\n";
    echo "=================================\n";

    $key4 = LockKey::fromString('helper-lock');
    $counter = 0;

    $result = $lockService1->withLock($key4, function() use (&$counter) {
        $counter++;
        echo "✅ Inside withLock() callback: counter = {$counter}\n";
        return "operation-result";
    });

    echo "✅ withLock() returned: {$result}\n";
    echo "✅ Counter after operation: {$counter}\n";

    // Verify lock was released
    $acquired2 = $lockService2->tryLock($key4);
    echo $acquired2 ? "✅ Lock was automatically released by withLock()\n" : "❌ Lock should have been released\n";
    $lockService2->unlock($key4);
    echo "\n";

    // Test 5: tryWithLock helper (non-blocking)
    echo "Test 5: tryWithLock() Helper (Non-Blocking)\n";
    echo "============================================\n";

    $key5 = LockKey::fromString('try-helper-lock');

    // Connection 1 holds lock
    $lockService1->lock($key5);
    echo "✅ Connection 1: Lock acquired\n";

    // Connection 2 tries with tryWithLock (should return null)
    $result2 = $lockService2->tryWithLock($key5, function() {
        echo "❌ This callback should NOT execute\n";
        return "should-not-happen";
    });

    echo ($result2 === null) ? "✅ Connection 2: tryWithLock() returned null (lock held by other connection)\n" : "❌ Should have returned null\n";

    $lockService1->unlock($key5);
    echo "✅ Connection 1: Lock released\n";

    // Now Connection 2 tries again (should succeed)
    $result2 = $lockService2->tryWithLock($key5, function() {
        echo "✅ Inside tryWithLock() callback\n";
        return "success";
    });

    echo ($result2 === "success") ? "✅ Connection 2: tryWithLock() executed and returned result\n" : "❌ Should have executed callback\n";
    echo "\n";

    // Test 6: Lock key generation methods
    echo "Test 6: Lock Key Generation Methods\n";
    echo "====================================\n";

    $stringKey = LockKey::fromString('my-resource');
    echo "✅ String-based key: {$stringKey->toInt()}\n";

    $intKey = LockKey::fromInt(999999);
    echo "✅ Integer-based key: {$intKey->toInt()}\n";

    $pairKey = LockKey::fromPair(100, 200);
    echo "✅ Pair-based key: {$pairKey->toInt()}\n";

    // Verify same string produces same key (deterministic)
    $stringKey2 = LockKey::fromString('my-resource');
    echo $stringKey->equals($stringKey2) ? "✅ String keys are deterministic (same string = same key)\n" : "❌ Keys should match\n";
    echo "\n";

    // Test 7: withTransactionLock helper
    echo "Test 7: withTransactionLock() Helper\n";
    echo "====================================\n";

    $key7 = LockKey::fromString('transaction-helper');

    // Create a test table for transaction
    $pdo1->exec("DROP TABLE IF EXISTS test_lock_counter");
    $pdo1->exec("CREATE TABLE test_lock_counter (value INTEGER)");
    $pdo1->exec("INSERT INTO test_lock_counter VALUES (0)");

    $result = $lockService1->withTransactionLock($key7, function() use ($pdo1) {
        // Read current value
        $stmt = $pdo1->query("SELECT value FROM test_lock_counter");
        $current = $stmt->fetch(PDO::FETCH_ASSOC)['value'];

        // Increment
        $pdo1->exec("UPDATE test_lock_counter SET value = " . ($current + 1));

        echo "✅ Inside withTransactionLock(): incremented counter from {$current} to " . ($current + 1) . "\n";

        return $current + 1;
    });

    echo "✅ withTransactionLock() committed and returned: {$result}\n";

    // Verify transaction was committed
    $stmt = $pdo1->query("SELECT value FROM test_lock_counter");
    $final = $stmt->fetch(PDO::FETCH_ASSOC)['value'];
    echo "✅ Final counter value in database: {$final}\n";

    // Cleanup
    $pdo1->exec("DROP TABLE test_lock_counter");
    echo "\n";

    // Test 8: unlockAll() functionality
    echo "Test 8: unlockAll() Functionality\n";
    echo "==================================\n";

    $key8a = LockKey::fromString('multi-lock-1');
    $key8b = LockKey::fromString('multi-lock-2');
    $key8c = LockKey::fromString('multi-lock-3');

    // Acquire multiple locks
    $lockService1->lock($key8a);
    $lockService1->lock($key8b);
    $lockService1->lock($key8c);
    echo "✅ Connection 1: Acquired 3 locks\n";

    // Verify locks are held
    $locked = !$lockService2->tryLock($key8a) && !$lockService2->tryLock($key8b) && !$lockService2->tryLock($key8c);
    echo $locked ? "✅ Connection 2: All 3 locks are correctly held by Connection 1\n" : "❌ Locks should be held\n";

    // Release all locks at once
    $lockService1->unlockAll();
    echo "✅ Connection 1: Released all locks with unlockAll()\n";

    // Verify all locks are released
    $released = $lockService2->tryLock($key8a) && $lockService2->tryLock($key8b) && $lockService2->tryLock($key8c);
    echo $released ? "✅ Connection 2: All 3 locks were successfully released\n" : "❌ All locks should be released\n";

    $lockService2->unlock($key8a);
    $lockService2->unlock($key8b);
    $lockService2->unlock($key8c);
    echo "\n";

    echo "✅ All Advisory Lock tests passed!\n";
    echo "\nSummary:\n";
    echo "========\n";
    echo "✅ Basic lock acquisition and release works\n";
    echo "✅ Non-blocking tryLock() works correctly\n";
    echo "✅ Transaction-scoped locks auto-release on commit\n";
    echo "✅ withLock() helper method works\n";
    echo "✅ tryWithLock() non-blocking helper works\n";
    echo "✅ Multiple lock key generation methods work\n";
    echo "✅ withTransactionLock() helper with auto-commit works\n";
    echo "✅ unlockAll() releases multiple locks\n";

} catch (\Exception $e) {
    echo "❌ Error: " . $e->getMessage() . "\n";
    echo "Stack trace:\n" . $e->getTraceAsString() . "\n";
    exit(1);
}
