<?php

declare(strict_types=1);

use App\Framework\Database\ValueObjects\DatabaseName;

describe('DatabaseName', function () {
    it('creates database name from string', function () {
        $dbName = DatabaseName::fromString('myapp_production');

        expect($dbName->value)->toBe('myapp_production');
        expect($dbName->toString())->toBe('myapp_production');
    });

    it('validates database name format', function () {
        // Valid names - should not throw
        DatabaseName::fromString('myapp');
        DatabaseName::fromString('my_app_db');
        DatabaseName::fromString('_temp_db');
        DatabaseName::fromString('db123');

        expect(true)->toBeTrue(); // Validation passed

        // Invalid names - should throw
        try {
            DatabaseName::fromString('');
            expect(false)->toBeTrue('Should have thrown for empty name');
        } catch (\InvalidArgumentException $e) {
            expect($e->getMessage())->toContain('cannot be empty');
        }

        try {
            DatabaseName::fromString('123invalid');
            expect(false)->toBeTrue('Should have thrown for name starting with number');
        } catch (\InvalidArgumentException $e) {
            expect($e->getMessage())->toContain('must start with a letter or underscore');
        }

        try {
            DatabaseName::fromString('invalid-name');
            expect(false)->toBeTrue('Should have thrown for name with hyphen');
        } catch (\InvalidArgumentException $e) {
            expect($e->getMessage())->toContain('can only contain letters, numbers, and underscores');
        }

        try {
            DatabaseName::fromString('invalid name');
            expect(false)->toBeTrue('Should have thrown for name with space');
        } catch (\InvalidArgumentException $e) {
            expect($e->getMessage())->toContain('can only contain letters, numbers, and underscores');
        }
    });

    it('validates maximum length', function () {
        // Valid length
        $validName = str_repeat('a', 64);
        DatabaseName::fromString($validName); // Should not throw

        // Too long
        $tooLong = str_repeat('a', 65);
        try {
            DatabaseName::fromString($tooLong);
            expect(false)->toBeTrue('Should have thrown for name exceeding max length');
        } catch (\InvalidArgumentException $e) {
            expect($e->getMessage())->toContain('exceeds maximum length');
        }
    });

    it('detects SQL injection attempts', function () {
        // Note: SQL injection attempts are caught by format validation
        // since they contain invalid characters (quotes, hyphens, spaces, etc.)

        try {
            DatabaseName::fromString("myapp'; DROP DATABASE--");
            expect(false)->toBeTrue('Should have thrown for SQL injection attempt');
        } catch (\InvalidArgumentException $e) {
            expect($e->getMessage())->toBeString();
        }

        try {
            DatabaseName::fromString('myapp UNION SELECT');
            expect(false)->toBeTrue('Should have thrown for SQL injection attempt');
        } catch (\InvalidArgumentException $e) {
            expect($e->getMessage())->toBeString();
        }

        try {
            DatabaseName::fromString('myapp/*comment*/');
            expect(false)->toBeTrue('Should have thrown for SQL injection attempt');
        } catch (\InvalidArgumentException $e) {
            expect($e->getMessage())->toBeString();
        }
    });

    it('quotes database names for different platforms', function () {
        $dbName = DatabaseName::fromString('myapp');

        expect($dbName->quoted('mysql'))->toBe('`myapp`');
        expect($dbName->quoted('postgresql'))->toBe('"myapp"');
        expect($dbName->quoted('postgres'))->toBe('"myapp"');
        expect($dbName->quoted('pgsql'))->toBe('"myapp"');
        expect($dbName->quoted('sqlite'))->toBe('"myapp"');
        expect($dbName->quoted())->toBe('`myapp`'); // Default MySQL
        expect($dbName->quoted('unknown'))->toBe('`myapp`'); // Fallback to MySQL
    });

    it('compares database names for equality', function () {
        $db1 = DatabaseName::fromString('myapp');
        $db2 = DatabaseName::fromString('myapp');
        $db3 = DatabaseName::fromString('MYAPP'); // Different case
        $db4 = DatabaseName::fromString('other_db');

        expect($db1->equals($db2))->toBeTrue();
        expect($db1->equals($db3))->toBeTrue(); // Case-insensitive
        expect($db1->equals($db4))->toBeFalse();
    });

    it('matches database name patterns', function () {
        $dbName = DatabaseName::fromString('myapp_production');

        expect($dbName->matches('myapp_*'))->toBeTrue();
        expect($dbName->matches('*_production'))->toBeTrue();
        expect($dbName->matches('myapp_production'))->toBeTrue();
        expect($dbName->matches('other_*'))->toBeFalse();
    });

    it('detects reserved SQL keywords', function () {
        $dbName = DatabaseName::fromString('myapp');
        expect($dbName->isReservedKeyword())->toBeFalse();
    });

    it('converts to lowercase', function () {
        $dbName = DatabaseName::fromString('MyApp_Production');

        expect($dbName->toLower())->toBe('myapp_production');
    });

    it('converts to uppercase', function () {
        $dbName = DatabaseName::fromString('myapp_production');

        expect($dbName->toUpper())->toBe('MYAPP_PRODUCTION');
    });

    it('checks for database name prefix', function () {
        $dbName = DatabaseName::fromString('myapp_production');

        expect($dbName->hasPrefix('myapp_'))->toBeTrue();
        expect($dbName->hasPrefix('other_'))->toBeFalse();
    });

    it('checks for database name suffix', function () {
        $dbName = DatabaseName::fromString('myapp_production');

        expect($dbName->hasSuffix('_production'))->toBeTrue();
        expect($dbName->hasSuffix('_staging'))->toBeFalse();
    });

    it('adds prefix to database name', function () {
        $dbName = DatabaseName::fromString('myapp');
        $prefixed = $dbName->withPrefix('dev_');

        expect($prefixed->value)->toBe('dev_myapp');
        expect($prefixed->toString())->toBe('dev_myapp');

        // Original unchanged (immutable)
        expect($dbName->value)->toBe('myapp');
    });

    it('removes prefix from database name', function () {
        $dbName = DatabaseName::fromString('dev_myapp');
        $unprefixed = $dbName->withoutPrefix('dev_');

        expect($unprefixed->value)->toBe('myapp');

        // Removing non-existent prefix returns same instance
        $same = $unprefixed->withoutPrefix('prod_');
        expect($same->value)->toBe('myapp');
    });

    it('adds suffix to database name', function () {
        $dbName = DatabaseName::fromString('myapp');
        $suffixed = $dbName->withSuffix('_production');

        expect($suffixed->value)->toBe('myapp_production');

        // Original unchanged (immutable)
        expect($dbName->value)->toBe('myapp');
    });

    it('removes suffix from database name', function () {
        $dbName = DatabaseName::fromString('myapp_production');
        $unsuffixed = $dbName->withoutSuffix('_production');

        expect($unsuffixed->value)->toBe('myapp');

        // Removing non-existent suffix returns same instance
        $same = $unsuffixed->withoutSuffix('_staging');
        expect($same->value)->toBe('myapp');
    });

    it('detects environment suffixes', function () {
        $prodDb = DatabaseName::fromString('myapp_production');
        expect($prodDb->getEnvironmentSuffix())->toBe('production');

        $stagingDb = DatabaseName::fromString('myapp_staging');
        expect($stagingDb->getEnvironmentSuffix())->toBe('staging');

        $testDb = DatabaseName::fromString('myapp_test');
        expect($testDb->getEnvironmentSuffix())->toBe('test');

        $devDb = DatabaseName::fromString('myapp_development');
        expect($devDb->getEnvironmentSuffix())->toBe('development');

        $localDb = DatabaseName::fromString('myapp_local');
        expect($localDb->getEnvironmentSuffix())->toBe('local');

        $noEnvDb = DatabaseName::fromString('myapp');
        expect($noEnvDb->getEnvironmentSuffix())->toBeNull();
    });

    it('detects test databases', function () {
        $testDb1 = DatabaseName::fromString('myapp_test');
        expect($testDb1->isTestDatabase())->toBeTrue();

        $testDb2 = DatabaseName::fromString('test_myapp');
        expect($testDb2->isTestDatabase())->toBeTrue();

        $prodDb = DatabaseName::fromString('myapp_production');
        expect($prodDb->isTestDatabase())->toBeFalse();
    });

    it('converts to string via magic method', function () {
        $dbName = DatabaseName::fromString('myapp');

        expect((string) $dbName)->toBe('myapp');
    });

    it('is immutable', function () {
        $original = DatabaseName::fromString('myapp');
        $prefixed = $original->withPrefix('dev_');
        $suffixed = $original->withSuffix('_prod');

        // Original remains unchanged
        expect($original->value)->toBe('myapp');

        // New instances created - values should differ
        expect($prefixed->value)->toBe('dev_myapp');
        expect($suffixed->value)->toBe('myapp_prod');

        // Verify immutability by checking original hasn't changed
        expect($original->value)->toBe('myapp');
    });

    it('handles edge cases correctly', function () {
        // Single character (valid if letter or underscore)
        $a = DatabaseName::fromString('a');
        expect($a->value)->toBe('a');

        $underscore = DatabaseName::fromString('_');
        expect($underscore->value)->toBe('_');

        // Underscore-only prefix
        $dbName = DatabaseName::fromString('_temp_myapp');
        expect($dbName->value)->toBe('_temp_myapp');

        // Numbers in name (but not at start)
        $dbName = DatabaseName::fromString('myapp_123_db');
        expect($dbName->value)->toBe('myapp_123_db');
    });
});
