<?php

declare(strict_types=1);

use App\Framework\Database\ValueObjects\IndexName;
use App\Framework\Database\ValueObjects\TableName;
use App\Framework\Database\ValueObjects\ColumnName;

describe('IndexName', function () {
    it('creates index name from string', function () {
        $index = IndexName::fromString('idx_users_email');

        expect($index->value)->toBe('idx_users_email');
        expect($index->toString())->toBe('idx_users_email');
    });

    it('creates PRIMARY KEY index', function () {
        $primary = IndexName::primary();

        expect($primary->value)->toBe('PRIMARY');
        expect($primary->isPrimary())->toBeTrue();
    });

    it('validates index name format', function () {
        // Valid names
        expect(fn() => IndexName::fromString('idx_users_email'))->not->toThrow(\InvalidArgumentException::class);
        expect(fn() => IndexName::fromString('unique_users_email'))->not->toThrow(\InvalidArgumentException::class);
        expect(fn() => IndexName::fromString('_temp_index'))->not->toThrow(\InvalidArgumentException::class);
        expect(fn() => IndexName::fromString('index123'))->not->toThrow(\InvalidArgumentException::class);

        // Invalid names
        expect(fn() => IndexName::fromString(''))
            ->toThrow(\InvalidArgumentException::class, 'cannot be empty');

        expect(fn() => IndexName::fromString('123invalid'))
            ->toThrow(\InvalidArgumentException::class, 'must start with a letter or underscore');

        expect(fn() => IndexName::fromString('invalid-name'))
            ->toThrow(\InvalidArgumentException::class, 'can only contain letters, numbers, and underscores');
    });

    it('allows PRIMARY as special case', function () {
        // PRIMARY is always valid
        expect(fn() => IndexName::fromString('PRIMARY'))->not->toThrow(\InvalidArgumentException::class);
        expect(fn() => IndexName::fromString('primary'))->not->toThrow(\InvalidArgumentException::class);
    });

    it('validates maximum length', function () {
        $validName = str_repeat('a', 64);
        expect(fn() => IndexName::fromString($validName))->not->toThrow(\InvalidArgumentException::class);

        $tooLong = str_repeat('a', 65);
        expect(fn() => IndexName::fromString($tooLong))
            ->toThrow(\InvalidArgumentException::class, '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.)

        expect(fn() => IndexName::fromString("idx'; DROP TABLE--"))
            ->toThrow(\InvalidArgumentException::class);

        expect(fn() => IndexName::fromString('idx UNION SELECT'))
            ->toThrow(\InvalidArgumentException::class);
    });

    it('quotes index names for different platforms', function () {
        $index = IndexName::fromString('idx_users_email');

        expect($index->quoted('mysql'))->toBe('`idx_users_email`');
        expect($index->quoted('postgresql'))->toBe('"idx_users_email"');
        expect($index->quoted('postgres'))->toBe('"idx_users_email"');
        expect($index->quoted('pgsql'))->toBe('"idx_users_email"');
        expect($index->quoted('sqlite'))->toBe('"idx_users_email"');
        expect($index->quoted())->toBe('`idx_users_email`'); // Default MySQL
    });

    it('never quotes PRIMARY KEY', function () {
        $primary = IndexName::primary();

        expect($primary->quoted('mysql'))->toBe('PRIMARY KEY');
        expect($primary->quoted('postgresql'))->toBe('PRIMARY KEY');
        expect($primary->quoted('sqlite'))->toBe('PRIMARY KEY');
    });

    it('compares index names for equality', function () {
        $idx1 = IndexName::fromString('idx_users_email');
        $idx2 = IndexName::fromString('idx_users_email');
        $idx3 = IndexName::fromString('IDX_USERS_EMAIL'); // Different case
        $idx4 = IndexName::fromString('idx_orders_id');

        expect($idx1->equals($idx2))->toBeTrue();
        expect($idx1->equals($idx3))->toBeTrue(); // Case-insensitive
        expect($idx1->equals($idx4))->toBeFalse();
    });

    it('matches index name patterns', function () {
        $index = IndexName::fromString('idx_users_email');

        expect($index->matches('idx_*'))->toBeTrue();
        expect($index->matches('*_email'))->toBeTrue();
        expect($index->matches('idx_users_*'))->toBeTrue();
        expect($index->matches('unique_*'))->toBeFalse();
    });

    it('detects reserved SQL keywords', function () {
        $index = IndexName::fromString('idx_users_email');
        expect($index->isReservedKeyword())->toBeFalse();
    });

    it('converts to lowercase', function () {
        $index = IndexName::fromString('IDX_Users_Email');

        expect($index->toLower())->toBe('idx_users_email');
    });

    it('checks for index name prefix', function () {
        $index = IndexName::fromString('idx_users_email');

        expect($index->hasPrefix('idx_'))->toBeTrue();
        expect($index->hasPrefix('unique_'))->toBeFalse();
    });

    it('checks for index name suffix', function () {
        $index = IndexName::fromString('idx_users_email');

        expect($index->hasSuffix('_email'))->toBeTrue();
        expect($index->hasSuffix('_id'))->toBeFalse();
    });

    it('detects unique indexes', function () {
        $uniqueIdx1 = IndexName::fromString('unique_users_email');
        $uniqueIdx2 = IndexName::fromString('idx_users_email_unique');
        $uniqueIdx3 = IndexName::fromString('idx_unique_constraint_users');
        $normalIdx = IndexName::fromString('idx_users_email');

        expect($uniqueIdx1->isUniqueIndex())->toBeTrue();
        expect($uniqueIdx2->isUniqueIndex())->toBeTrue();
        expect($uniqueIdx3->isUniqueIndex())->toBeTrue();
        expect($normalIdx->isUniqueIndex())->toBeFalse();
    });

    it('detects full-text indexes', function () {
        $fulltextIdx1 = IndexName::fromString('fulltext_posts_content');
        $fulltextIdx2 = IndexName::fromString('idx_posts_content_fulltext');
        $fulltextIdx3 = IndexName::fromString('idx_fulltext_search');
        $normalIdx = IndexName::fromString('idx_posts_title');

        expect($fulltextIdx1->isFullTextIndex())->toBeTrue();
        expect($fulltextIdx2->isFullTextIndex())->toBeTrue();
        expect($fulltextIdx3->isFullTextIndex())->toBeTrue();
        expect($normalIdx->isFullTextIndex())->toBeFalse();
    });

    it('generates conventional index names for columns', function () {
        $table = TableName::fromString('users');
        $emailColumn = ColumnName::fromString('email');

        $index = IndexName::forColumns($table, $emailColumn);

        expect($index->value)->toBe('idx_users_email');
        expect($index->hasPrefix('idx_'))->toBeTrue();
    });

    it('generates multi-column index names', function () {
        $table = TableName::fromString('users');
        $firstNameColumn = ColumnName::fromString('first_name');
        $lastNameColumn = ColumnName::fromString('last_name');

        $index = IndexName::forColumns($table, $firstNameColumn, $lastNameColumn);

        expect($index->value)->toBe('idx_users_first_name_last_name');
    });

    it('generates unique constraint names', function () {
        $table = TableName::fromString('users');
        $emailColumn = ColumnName::fromString('email');

        $index = IndexName::uniqueFor($table, $emailColumn);

        expect($index->value)->toBe('unique_users_email');
        expect($index->isUniqueIndex())->toBeTrue();
    });

    it('generates multi-column unique constraint names', function () {
        $table = TableName::fromString('users');
        $emailColumn = ColumnName::fromString('email');
        $tenantIdColumn = ColumnName::fromString('tenant_id');

        $index = IndexName::uniqueFor($table, $emailColumn, $tenantIdColumn);

        expect($index->value)->toBe('unique_users_email_tenant_id');
        expect($index->isUniqueIndex())->toBeTrue();
    });

    it('generates foreign key index names', function () {
        $table = TableName::fromString('orders');
        $userIdColumn = ColumnName::fromString('user_id');

        $index = IndexName::foreignKeyFor($table, $userIdColumn);

        expect($index->value)->toBe('fk_orders_user_id');
        expect($index->hasPrefix('fk_'))->toBeTrue();
    });

    it('converts to string via magic method', function () {
        $index = IndexName::fromString('idx_users_email');

        expect((string) $index)->toBe('idx_users_email');
    });

    it('is immutable', function () {
        $index = IndexName::fromString('idx_users_email');
        $value = $index->value;

        // Value cannot be changed
        expect($index->value)->toBe('idx_users_email');
        expect($value)->toBe('idx_users_email');
    });

    it('handles edge cases correctly', function () {
        // Single character (valid if letter or underscore)
        expect(fn() => IndexName::fromString('a'))->not->toThrow(\InvalidArgumentException::class);
        expect(fn() => IndexName::fromString('_'))->not->toThrow(\InvalidArgumentException::class);

        // Underscore prefix
        $index = IndexName::fromString('_temp_index');
        expect($index->value)->toBe('_temp_index');

        // Numbers in name
        $index = IndexName::fromString('idx_123');
        expect($index->value)->toBe('idx_123');
    });

    it('detects PRIMARY in different cases', function () {
        $primary1 = IndexName::fromString('PRIMARY');
        $primary2 = IndexName::fromString('primary');
        $primary3 = IndexName::fromString('Primary');

        expect($primary1->isPrimary())->toBeTrue();
        expect($primary2->isPrimary())->toBeTrue();
        expect($primary3->isPrimary())->toBeTrue();
    });

    it('factory methods create valid index names', function () {
        $table = TableName::fromString('users');
        $emailColumn = ColumnName::fromString('email');

        // All factory methods should produce valid names
        expect(fn() => IndexName::forColumns($table, $emailColumn))->not->toThrow(\InvalidArgumentException::class);
        expect(fn() => IndexName::uniqueFor($table, $emailColumn))->not->toThrow(\InvalidArgumentException::class);
        expect(fn() => IndexName::foreignKeyFor($table, $emailColumn))->not->toThrow(\InvalidArgumentException::class);
    });

    it('combines with TableName and ColumnName correctly', function () {
        $table = TableName::fromString('users');
        $emailColumn = ColumnName::fromString('email');
        $statusColumn = ColumnName::fromString('status');

        $index = IndexName::forColumns($table, $emailColumn, $statusColumn);

        expect($index->value)->toContain('users');
        expect($index->value)->toContain('email');
        expect($index->value)->toContain('status');
    });
});
