<?php

declare(strict_types=1);

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

describe('ConstraintName', function () {
    it('creates constraint name from string', function () {
        $constraint = ConstraintName::fromString('fk_users_company_id');

        expect($constraint->value)->toBe('fk_users_company_id');
        expect($constraint->toString())->toBe('fk_users_company_id');
    });

    it('creates PRIMARY KEY constraint', function () {
        $primaryKey = ConstraintName::primaryKey();

        expect($primaryKey->value)->toBe('PRIMARY KEY');
        expect($primaryKey->isPrimaryKey())->toBeTrue();
    });

    it('creates UNIQUE constraint with conventional naming', function () {
        $table = TableName::fromString('users');
        $emailColumn = ColumnName::fromString('email');

        $unique = ConstraintName::unique($table, $emailColumn);

        expect($unique->value)->toBe('uq_users_email');
        expect($unique->isUnique())->toBeTrue();
    });

    it('creates multi-column UNIQUE constraint', function () {
        $table = TableName::fromString('users');
        $emailColumn = ColumnName::fromString('email');
        $tenantColumn = ColumnName::fromString('tenant_id');

        $unique = ConstraintName::unique($table, $emailColumn, $tenantColumn);

        expect($unique->value)->toBe('uq_users_email_tenant_id');
        expect($unique->isUnique())->toBeTrue();
    });

    it('creates FOREIGN KEY constraint with conventional naming', function () {
        $table = TableName::fromString('orders');
        $column = ColumnName::fromString('user_id');
        $refTable = TableName::fromString('users');

        $fk = ConstraintName::foreignKey($table, $column, $refTable);

        expect($fk->value)->toBe('fk_orders_user_id_users');
        expect($fk->isForeignKey())->toBeTrue();
    });

    it('creates CHECK constraint with conventional naming', function () {
        $table = TableName::fromString('products');

        $check = ConstraintName::check($table, 'positive_price');

        expect($check->value)->toBe('chk_products_positive_price');
        expect($check->isCheck())->toBeTrue();
    });

    it('validates constraint name format', function () {
        // Valid names - should not throw
        ConstraintName::fromString('fk_users_company');
        ConstraintName::fromString('uq_email');
        ConstraintName::fromString('chk_positive_amount');

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

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

        try {
            ConstraintName::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 {
            ConstraintName::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('allows keyword constraints as special cases', function () {
        // These are SQL keywords but valid constraints
        ConstraintName::fromString('PRIMARY KEY');
        ConstraintName::fromString('UNIQUE');
        ConstraintName::fromString('CHECK');
        ConstraintName::fromString('FOREIGN KEY');

        expect(true)->toBeTrue(); // All keyword constraints created successfully
    });

    it('validates maximum length', function () {
        // Valid length
        $validName = 'fk_' . str_repeat('a', 61);
        ConstraintName::fromString($validName); // Should not throw

        // Too long
        $tooLong = 'fk_' . str_repeat('a', 62);
        try {
            ConstraintName::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 with special characters are caught by format validation
        try {
            ConstraintName::fromString("fk'; DROP TABLE--");
            expect(false)->toBeTrue('Should have thrown for SQL injection attempt');
        } catch (\InvalidArgumentException $e) {
            expect($e->getMessage())->toBeString();
        }

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

    it('quotes constraint names for different platforms', function () {
        $constraint = ConstraintName::fromString('fk_users_company');

        expect($constraint->quoted('mysql'))->toBe('`fk_users_company`');
        expect($constraint->quoted('postgresql'))->toBe('"fk_users_company"');
        expect($constraint->quoted('sqlite'))->toBe('"fk_users_company"');
        expect($constraint->quoted())->toBe('`fk_users_company`'); // Default MySQL
    });

    it('never quotes keyword constraints', function () {
        $primaryKey = ConstraintName::primaryKey();
        $unique = ConstraintName::fromString('UNIQUE');
        $check = ConstraintName::fromString('CHECK');

        expect($primaryKey->quoted('mysql'))->toBe('PRIMARY KEY');
        expect($primaryKey->quoted('postgresql'))->toBe('PRIMARY KEY');
        expect($unique->quoted('mysql'))->toBe('UNIQUE');
        expect($check->quoted('postgresql'))->toBe('CHECK');
    });

    it('compares constraint names for equality', function () {
        $fk1 = ConstraintName::fromString('fk_users_company');
        $fk2 = ConstraintName::fromString('fk_users_company');
        $fk3 = ConstraintName::fromString('FK_USERS_COMPANY'); // Different case
        $fk4 = ConstraintName::fromString('fk_orders_user');

        expect($fk1->equals($fk2))->toBeTrue();
        expect($fk1->equals($fk3))->toBeTrue(); // Case-insensitive
        expect($fk1->equals($fk4))->toBeFalse();
    });

    it('matches constraint name patterns', function () {
        $constraint = ConstraintName::fromString('fk_users_company_id');

        expect($constraint->matches('fk_*'))->toBeTrue();
        expect($constraint->matches('*_company_id'))->toBeTrue();
        expect($constraint->matches('fk_users_*'))->toBeTrue();
        expect($constraint->matches('uq_*'))->toBeFalse();
    });

    it('detects reserved SQL keywords', function () {
        $constraint = ConstraintName::fromString('fk_users_company');
        expect($constraint->isReservedKeyword())->toBeFalse();
    });

    it('converts to lowercase', function () {
        $constraint = ConstraintName::fromString('FK_Users_Company');

        expect($constraint->toLower())->toBe('fk_users_company');
    });

    it('checks for constraint name prefix', function () {
        $constraint = ConstraintName::fromString('fk_users_company');

        expect($constraint->hasPrefix('fk_'))->toBeTrue();
        expect($constraint->hasPrefix('uq_'))->toBeFalse();
    });

    it('checks for constraint name suffix', function () {
        $constraint = ConstraintName::fromString('fk_users_company_id');

        expect($constraint->hasSuffix('_id'))->toBeTrue();
        expect($constraint->hasSuffix('_key'))->toBeFalse();
    });

    it('detects PRIMARY KEY constraints', function () {
        $pk1 = ConstraintName::primaryKey();
        expect($pk1->isPrimaryKey())->toBeTrue();

        $pk2 = ConstraintName::fromString('PRIMARY');
        expect($pk2->isPrimaryKey())->toBeTrue();

        $fk = ConstraintName::fromString('fk_users_company');
        expect($fk->isPrimaryKey())->toBeFalse();
    });

    it('detects FOREIGN KEY constraints', function () {
        $fk1 = ConstraintName::fromString('fk_users_company');
        expect($fk1->isForeignKey())->toBeTrue();

        $fk2 = ConstraintName::fromString('FOREIGN KEY');
        expect($fk2->isForeignKey())->toBeTrue();

        $uq = ConstraintName::fromString('uq_email');
        expect($uq->isForeignKey())->toBeFalse();
    });

    it('detects UNIQUE constraints', function () {
        $uq1 = ConstraintName::fromString('uq_email');
        expect($uq1->isUnique())->toBeTrue();

        $uq2 = ConstraintName::fromString('unique_users_email');
        expect($uq2->isUnique())->toBeTrue();

        $uq3 = ConstraintName::fromString('UNIQUE');
        expect($uq3->isUnique())->toBeTrue();

        $fk = ConstraintName::fromString('fk_users_company');
        expect($fk->isUnique())->toBeFalse();
    });

    it('detects CHECK constraints', function () {
        $chk1 = ConstraintName::fromString('chk_positive_amount');
        expect($chk1->isCheck())->toBeTrue();

        $chk2 = ConstraintName::fromString('check_valid_status');
        expect($chk2->isCheck())->toBeTrue();

        $chk3 = ConstraintName::fromString('CHECK');
        expect($chk3->isCheck())->toBeTrue();

        $fk = ConstraintName::fromString('fk_users_company');
        expect($fk->isCheck())->toBeFalse();
    });

    it('detects DEFAULT constraints', function () {
        $df1 = ConstraintName::fromString('df_created_at');
        expect($df1->isDefault())->toBeTrue();

        $df2 = ConstraintName::fromString('default_status');
        expect($df2->isDefault())->toBeTrue();

        $df3 = ConstraintName::fromString('DEFAULT');
        expect($df3->isDefault())->toBeTrue();

        $fk = ConstraintName::fromString('fk_users_company');
        expect($fk->isDefault())->toBeFalse();
    });

    it('identifies keyword constraints', function () {
        $pk = ConstraintName::primaryKey();
        expect($pk->isKeywordConstraint())->toBeTrue();

        $unique = ConstraintName::fromString('UNIQUE');
        expect($unique->isKeywordConstraint())->toBeTrue();

        $check = ConstraintName::fromString('CHECK');
        expect($check->isKeywordConstraint())->toBeTrue();

        $custom = ConstraintName::fromString('fk_users_company');
        expect($custom->isKeywordConstraint())->toBeFalse();
    });

    it('determines constraint type', function () {
        $pk = ConstraintName::primaryKey();
        expect($pk->getType())->toBe('primary_key');

        $fk = ConstraintName::fromString('fk_users_company');
        expect($fk->getType())->toBe('foreign_key');

        $uq = ConstraintName::fromString('uq_email');
        expect($uq->getType())->toBe('unique');

        $chk = ConstraintName::fromString('chk_positive');
        expect($chk->getType())->toBe('check');

        $df = ConstraintName::fromString('df_status');
        expect($df->getType())->toBe('default');

        $custom = ConstraintName::fromString('my_custom_constraint');
        expect($custom->getType())->toBe('custom');
    });

    it('converts to string via magic method', function () {
        $constraint = ConstraintName::fromString('fk_users_company');

        expect((string) $constraint)->toBe('fk_users_company');
    });

    it('is immutable', function () {
        $table = TableName::fromString('users');
        $column = ColumnName::fromString('email');

        $unique = ConstraintName::unique($table, $column);
        $value = $unique->value;

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

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

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

        // Underscore prefix
        $constraint = ConstraintName::fromString('_temp_constraint');
        expect($constraint->value)->toBe('_temp_constraint');

        // Numbers in name
        $constraint = ConstraintName::fromString('fk_table_123');
        expect($constraint->value)->toBe('fk_table_123');
    });

    it('factory methods create valid constraint names', function () {
        $table = TableName::fromString('users');
        $column = ColumnName::fromString('email');
        $refTable = TableName::fromString('companies');

        // All factory methods should produce valid names
        ConstraintName::primaryKey();
        ConstraintName::unique($table, $column);
        ConstraintName::foreignKey($table, $column, $refTable);
        ConstraintName::check($table, 'valid_status');

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