<?php

declare(strict_types=1);

use App\Framework\Database\Validation\DatabaseIdentifierValidator;

describe('DatabaseIdentifierValidator', function () {
    beforeEach(function () {
        $this->validator = new DatabaseIdentifierValidator();
    });

    it('validates correct identifier format', function () {
        // Valid identifiers
        expect(fn() => $this->validator->validate('users', 'table'))->not->toThrow(\InvalidArgumentException::class);
        expect(fn() => $this->validator->validate('user_profiles', 'table'))->not->toThrow(\InvalidArgumentException::class);
        expect(fn() => $this->validator->validate('_temp_table', 'table'))->not->toThrow(\InvalidArgumentException::class);
        expect(fn() => $this->validator->validate('table123', 'table'))->not->toThrow(\InvalidArgumentException::class);
    });

    it('throws on empty identifier', function () {
        expect(fn() => $this->validator->validate('', 'table'))
            ->toThrow(\InvalidArgumentException::class, 'Table name cannot be empty');

        expect(fn() => $this->validator->validate('', 'column'))
            ->toThrow(\InvalidArgumentException::class, 'Column name cannot be empty');
    });

    it('throws on identifier exceeding maximum length', function () {
        $validLength = str_repeat('a', 64);
        expect(fn() => $this->validator->validate($validLength, 'table'))->not->toThrow(\InvalidArgumentException::class);

        $tooLong = str_repeat('a', 65);
        expect(fn() => $this->validator->validate($tooLong, 'table'))
            ->toThrow(\InvalidArgumentException::class, 'exceeds maximum length');
    });

    it('returns maximum length constant', function () {
        expect($this->validator->getMaxLength())->toBe(64);
    });

    it('throws on identifier not starting with letter or underscore', function () {
        expect(fn() => $this->validator->validate('123invalid', 'table'))
            ->toThrow(\InvalidArgumentException::class, 'must start with a letter or underscore');

        expect(fn() => $this->validator->validate('-invalid', 'table'))
            ->toThrow(\InvalidArgumentException::class, 'must start with a letter or underscore');

        expect(fn() => $this->validator->validate('$invalid', 'table'))
            ->toThrow(\InvalidArgumentException::class, 'must start with a letter or underscore');
    });

    it('throws on invalid format with special characters', function () {
        expect(fn() => $this->validator->validate('invalid-name', 'table'))
            ->toThrow(\InvalidArgumentException::class, 'can only contain letters, numbers, and underscores');

        expect(fn() => $this->validator->validate('invalid name', 'table'))
            ->toThrow(\InvalidArgumentException::class, 'can only contain letters, numbers, and underscores');

        expect(fn() => $this->validator->validate('invalid.name', 'table'))
            ->toThrow(\InvalidArgumentException::class, 'can only contain letters, numbers, and underscores');

        expect(fn() => $this->validator->validate('invalid@name', 'table'))
            ->toThrow(\InvalidArgumentException::class, 'can only contain letters, numbers, and underscores');
    });

    it('detects SQL injection patterns', function () {
        // Note: Most patterns are caught by format validation (invalid characters)
        // SQL injection protection works through:
        // 1. Format validation (alphanumeric + underscores only)
        // 2. Additional checks for metacharacters

        // SQL comments (also fails format validation due to -)
        expect(fn() => $this->validator->validate('users--comment', 'table'))
            ->toThrow(\InvalidArgumentException::class);

        // SQL comment block (also fails format validation due to /)
        expect(fn() => $this->validator->validate('users/*comment*/', 'table'))
            ->toThrow(\InvalidArgumentException::class);

        // Statement separator (also fails format validation)
        expect(fn() => $this->validator->validate('users;DROP', 'table'))
            ->toThrow(\InvalidArgumentException::class);

        // Quotes (fail format validation)
        expect(fn() => $this->validator->validate("users'test", 'table'))
            ->toThrow(\InvalidArgumentException::class);

        expect(fn() => $this->validator->validate('users"test', 'table'))
            ->toThrow(\InvalidArgumentException::class);

        // Backslash (fails format validation)
        expect(fn() => $this->validator->validate('users\\test', 'table'))
            ->toThrow(\InvalidArgumentException::class);
    });

    it('detects reserved SQL keywords', function () {
        expect($this->validator->isReservedKeyword('SELECT'))->toBeTrue();
        expect($this->validator->isReservedKeyword('select'))->toBeTrue(); // Case-insensitive
        expect($this->validator->isReservedKeyword('INSERT'))->toBeTrue();
        expect($this->validator->isReservedKeyword('UPDATE'))->toBeTrue();
        expect($this->validator->isReservedKeyword('DELETE'))->toBeTrue();
        expect($this->validator->isReservedKeyword('DROP'))->toBeTrue();
        expect($this->validator->isReservedKeyword('CREATE'))->toBeTrue();
        expect($this->validator->isReservedKeyword('ALTER'))->toBeTrue();
        expect($this->validator->isReservedKeyword('TABLE'))->toBeTrue();
        expect($this->validator->isReservedKeyword('INDEX'))->toBeTrue();
        expect($this->validator->isReservedKeyword('FROM'))->toBeTrue();
        expect($this->validator->isReservedKeyword('WHERE'))->toBeTrue();
        expect($this->validator->isReservedKeyword('UNION'))->toBeTrue();

        // Non-reserved words
        expect($this->validator->isReservedKeyword('users'))->toBeFalse();
        expect($this->validator->isReservedKeyword('email'))->toBeFalse();
        expect($this->validator->isReservedKeyword('profile'))->toBeFalse();
    });

    it('allows special values via allowedValues parameter', function () {
        // PRIMARY is a reserved keyword but still a valid identifier format
        // allowedValues can be used to explicitly allow values that might be reserved
        expect(fn() => $this->validator->validate('PRIMARY', 'index', ['PRIMARY']))
            ->not->toThrow(\InvalidArgumentException::class);

        // UNIQUE can be allowed
        expect(fn() => $this->validator->validate('UNIQUE', 'index', ['UNIQUE']))
            ->not->toThrow(\InvalidArgumentException::class);

        // Note: PRIMARY and UNIQUE are valid identifier formats (they pass all validation rules)
        // Reserved keyword checking is separate - use isReservedKeyword() for that
        expect(fn() => $this->validator->validate('PRIMARY', 'index', []))
            ->not->toThrow(\InvalidArgumentException::class);

        expect($this->validator->isReservedKeyword('PRIMARY'))->toBeTrue();
        expect($this->validator->isReservedKeyword('UNIQUE'))->toBeTrue();
    });

    it('uses custom type name in error messages', function () {
        try {
            $this->validator->validate('', 'custom_type');
            expect(false)->toBeTrue(); // Should not reach here
        } catch (InvalidArgumentException $e) {
            expect($e->getMessage())->toContain('Custom_type');
        }

        try {
            $this->validator->validate('123invalid', 'my_entity');
            expect(false)->toBeTrue();
        } catch (InvalidArgumentException $e) {
            expect($e->getMessage())->toContain('My_entity');
        }
    });

    it('validates identifiers with underscores correctly', function () {
        // Leading underscore
        expect(fn() => $this->validator->validate('_temp', 'table'))->not->toThrow(\InvalidArgumentException::class);
        expect(fn() => $this->validator->validate('__double', 'table'))->not->toThrow(\InvalidArgumentException::class);

        // Multiple underscores
        expect(fn() => $this->validator->validate('user_email_verified', 'column'))->not->toThrow(\InvalidArgumentException::class);

        // Trailing underscore
        expect(fn() => $this->validator->validate('temp_', 'table'))->not->toThrow(\InvalidArgumentException::class);
    });

    it('validates identifiers with numbers correctly', function () {
        // Numbers in middle
        expect(fn() => $this->validator->validate('table123test', 'table'))->not->toThrow(\InvalidArgumentException::class);

        // Numbers at end
        expect(fn() => $this->validator->validate('table123', 'table'))->not->toThrow(\InvalidArgumentException::class);

        // Only numbers after first character
        expect(fn() => $this->validator->validate('t123456789', 'table'))->not->toThrow(\InvalidArgumentException::class);

        // But not at start
        expect(fn() => $this->validator->validate('123table', 'table'))
            ->toThrow(\InvalidArgumentException::class);
    });

    it('is case-insensitive for reserved keywords', function () {
        expect($this->validator->isReservedKeyword('select'))->toBeTrue();
        expect($this->validator->isReservedKeyword('SELECT'))->toBeTrue();
        expect($this->validator->isReservedKeyword('SeLeCt'))->toBeTrue();
        expect($this->validator->isReservedKeyword('table'))->toBeTrue();
        expect($this->validator->isReservedKeyword('TABLE'))->toBeTrue();
    });

    it('validates identifiers that contain SQL keywords as substrings', function () {
        // Valid identifiers can contain SQL keywords as substrings
        // This is fine because they're alphanumeric and will be quoted in SQL

        expect(fn() => $this->validator->validate('usersunion', 'table'))->not->toThrow(\InvalidArgumentException::class);
        expect(fn() => $this->validator->validate('updated_at', 'column'))->not->toThrow(\InvalidArgumentException::class);
        expect(fn() => $this->validator->validate('created_at', 'column'))->not->toThrow(\InvalidArgumentException::class);
        expect(fn() => $this->validator->validate('deleted_at', 'column'))->not->toThrow(\InvalidArgumentException::class);
    });

    it('validates edge cases', function () {
        // Single character
        expect(fn() => $this->validator->validate('a', 'table'))->not->toThrow(\InvalidArgumentException::class);
        expect(fn() => $this->validator->validate('_', 'table'))->not->toThrow(\InvalidArgumentException::class);

        // Exactly 64 characters (max length)
        $maxLength = str_repeat('a', 64);
        expect(fn() => $this->validator->validate($maxLength, 'table'))->not->toThrow(\InvalidArgumentException::class);
    });

    it('provides contextual error messages', function () {
        try {
            $this->validator->validate('', 'table');
        } catch (InvalidArgumentException $e) {
            expect($e->getMessage())->toContain('Table');
            expect($e->getMessage())->toContain('cannot be empty');
        }

        try {
            $this->validator->validate('', 'column');
        } catch (InvalidArgumentException $e) {
            expect($e->getMessage())->toContain('Column');
            expect($e->getMessage())->toContain('cannot be empty');
        }

        try {
            $this->validator->validate('', 'index');
        } catch (InvalidArgumentException $e) {
            expect($e->getMessage())->toContain('Index');
            expect($e->getMessage())->toContain('cannot be empty');
        }
    });
});
