<?php

declare(strict_types=1);

use App\Framework\Database\ValueObjects\SchemaName;
use App\Framework\Database\ValueObjects\TableName;

describe('SchemaName', function () {
    it('creates schema name from string', function () {
        $schema = SchemaName::fromString('app_v1');

        expect($schema->value)->toBe('app_v1');
        expect($schema->toString())->toBe('app_v1');
    });

    it('creates default public schema', function () {
        $public = SchemaName::public();

        expect($public->value)->toBe('public');
        expect($public->isPublic())->toBeTrue();
    });

    it('creates information_schema', function () {
        $infoSchema = SchemaName::informationSchema();

        expect($infoSchema->value)->toBe('information_schema');
        expect($infoSchema->isSystemSchema())->toBeTrue();
    });

    it('creates pg_catalog', function () {
        $pgCatalog = SchemaName::pgCatalog();

        expect($pgCatalog->value)->toBe('pg_catalog');
        expect($pgCatalog->isSystemSchema())->toBeTrue();
    });

    it('validates schema name format', function () {
        // Valid names - should not throw
        SchemaName::fromString('app_v1');
        SchemaName::fromString('my_schema');
        SchemaName::fromString('_temp_schema');
        SchemaName::fromString('schema123');

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

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

        try {
            SchemaName::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 {
            SchemaName::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');
        }
    });

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

        // Too long
        $tooLong = str_repeat('a', 65);
        try {
            SchemaName::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
        try {
            SchemaName::fromString("app'; DROP SCHEMA--");
            expect(false)->toBeTrue('Should have thrown for SQL injection attempt');
        } catch (\InvalidArgumentException $e) {
            expect($e->getMessage())->toBeString();
        }

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

    it('quotes schema names for different platforms', function () {
        $schema = SchemaName::fromString('app_v1');

        expect($schema->quoted('postgresql'))->toBe('"app_v1"');
        expect($schema->quoted('postgres'))->toBe('"app_v1"');
        expect($schema->quoted('pgsql'))->toBe('"app_v1"');
        expect($schema->quoted('sqlite'))->toBe('"app_v1"');
        expect($schema->quoted('mysql'))->toBe('`app_v1`');
        expect($schema->quoted())->toBe('"app_v1"'); // Default PostgreSQL
    });

    it('qualifies table names', function () {
        $schema = SchemaName::fromString('app_v1');
        $table = TableName::fromString('users');

        expect($schema->qualifyTable($table, 'postgresql'))->toBe('"app_v1"."users"');
        expect($schema->qualifyTable($table, 'mysql'))->toBe('`app_v1`.`users`');
    });

    it('compares schema names for equality', function () {
        $schema1 = SchemaName::fromString('app_v1');
        $schema2 = SchemaName::fromString('app_v1');
        $schema3 = SchemaName::fromString('app_v2');

        expect($schema1->equals($schema2))->toBeTrue();
        expect($schema1->equals($schema3))->toBeFalse();

        // PostgreSQL schemas are case-sensitive
        $schemaLower = SchemaName::fromString('app');
        $schemaUpper = SchemaName::fromString('APP');
        expect($schemaLower->equals($schemaUpper))->toBeFalse();
    });

    it('matches schema name patterns', function () {
        $schema = SchemaName::fromString('app_v1');

        expect($schema->matches('app_*'))->toBeTrue();
        expect($schema->matches('*_v1'))->toBeTrue();
        expect($schema->matches('app_v1'))->toBeTrue();
        expect($schema->matches('other_*'))->toBeFalse();
    });

    it('detects reserved SQL keywords', function () {
        $schema = SchemaName::fromString('app_v1');
        expect($schema->isReservedKeyword())->toBeFalse();
    });

    it('converts to lowercase', function () {
        $schema = SchemaName::fromString('App_V1');

        expect($schema->toLower())->toBe('app_v1');
    });

    it('checks for schema name prefix', function () {
        $schema = SchemaName::fromString('app_v1');

        expect($schema->hasPrefix('app_'))->toBeTrue();
        expect($schema->hasPrefix('other_'))->toBeFalse();
    });

    it('checks for schema name suffix', function () {
        $schema = SchemaName::fromString('app_v1');

        expect($schema->hasSuffix('_v1'))->toBeTrue();
        expect($schema->hasSuffix('_v2'))->toBeFalse();
    });

    it('detects public schema', function () {
        $public = SchemaName::public();
        expect($public->isPublic())->toBeTrue();

        $custom = SchemaName::fromString('app_v1');
        expect($custom->isPublic())->toBeFalse();
    });

    it('detects system schemas', function () {
        $infoSchema = SchemaName::informationSchema();
        expect($infoSchema->isSystemSchema())->toBeTrue();

        $pgCatalog = SchemaName::pgCatalog();
        expect($pgCatalog->isSystemSchema())->toBeTrue();

        $pgSchema = SchemaName::fromString('pg_temp');
        expect($pgSchema->isSystemSchema())->toBeTrue();

        $customSchema = SchemaName::fromString('app_v1');
        expect($customSchema->isSystemSchema())->toBeFalse();
    });

    it('detects temporary schemas', function () {
        $tempSchema1 = SchemaName::fromString('temp_schema');
        expect($tempSchema1->isTemporary())->toBeTrue();

        $tempSchema2 = SchemaName::fromString('tmp_data');
        expect($tempSchema2->isTemporary())->toBeTrue();

        $tempSchema3 = SchemaName::fromString('schema_temp');
        expect($tempSchema3->isTemporary())->toBeTrue();

        $normalSchema = SchemaName::fromString('app_v1');
        expect($normalSchema->isTemporary())->toBeFalse();
    });

    it('adds prefix to schema name', function () {
        $schema = SchemaName::fromString('v1');
        $prefixed = $schema->withPrefix('app_');

        expect($prefixed->value)->toBe('app_v1');

        // Original unchanged (immutable)
        expect($schema->value)->toBe('v1');
    });

    it('removes prefix from schema name', function () {
        $schema = SchemaName::fromString('app_v1');
        $unprefixed = $schema->withoutPrefix('app_');

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

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

    it('converts to string via magic method', function () {
        $schema = SchemaName::fromString('app_v1');

        expect((string) $schema)->toBe('app_v1');
    });

    it('is immutable', function () {
        $original = SchemaName::fromString('v1');
        $prefixed = $original->withPrefix('app_');

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

        // New instance created - verify by value difference
        expect($prefixed->value)->toBe('app_v1');

        // Removing non-existent prefix returns same instance (optimization)
        $unprefixed = $original->withoutPrefix('app_');
        expect($unprefixed->value)->toBe('v1');

        // But removing actual prefix creates new instance
        $prefixedSchema = SchemaName::fromString('app_v1');
        $removedPrefix = $prefixedSchema->withoutPrefix('app_');
        expect($removedPrefix->value)->toBe('v1');
        expect($prefixedSchema->value)->toBe('app_v1'); // Original unchanged
    });

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

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

        // Underscore prefix
        $schema = SchemaName::fromString('_temp');
        expect($schema->value)->toBe('_temp');

        // Numbers in name
        $schema = SchemaName::fromString('app_123');
        expect($schema->value)->toBe('app_123');
    });

    it('factory methods create valid schemas', function () {
        SchemaName::public();
        SchemaName::informationSchema();
        SchemaName::pgCatalog();

        expect(true)->toBeTrue(); // All factory methods succeeded
    });
});
