<?php

declare(strict_types=1);

namespace App\Framework\Database\ValueObjects;

use App\Framework\Database\Validation\DatabaseIdentifierValidator;
use InvalidArgumentException;

/**
 * Value Object representing a validated database constraint name
 *
 * Constraints include:
 * - PRIMARY KEY
 * - FOREIGN KEY
 * - UNIQUE
 * - CHECK
 * - DEFAULT
 *
 * Ensures constraint names follow SQL naming conventions:
 * - Start with letter or underscore
 * - Contain only alphanumeric characters and underscores
 * - Maximum 64 characters (MySQL/PostgreSQL standard)
 * - No SQL injection patterns
 *
 * Immutable and type-safe replacement for string constraint names.
 */
final readonly class ConstraintName
{
    /**
     * @param string $value Validated constraint name
     * @throws InvalidArgumentException if name is invalid
     */
    private function __construct(
        public string $value
    ) {}

    /**
     * Create ConstraintName from string
     *
     * @throws InvalidArgumentException if name is invalid
     */
    public static function fromString(string $name): self
    {
        $validator = new DatabaseIdentifierValidator();

        // Allow special constraint keywords
        $allowedValues = ['PRIMARY KEY', 'UNIQUE', 'CHECK', 'FOREIGN KEY'];

        // For multi-word constraints, validate without spaces
        $nameToValidate = str_replace(' ', '_', $name);

        try {
            $validator->validate($nameToValidate, 'constraint', $allowedValues);
        } catch (InvalidArgumentException $e) {
            // If it's one of the allowed special values, bypass validation
            if (in_array(strtoupper($name), $allowedValues, true)) {
                return new self($name);
            }
            throw $e;
        }

        return new self($name);
    }

    /**
     * Create PRIMARY KEY constraint
     */
    public static function primaryKey(): self
    {
        return new self('PRIMARY KEY');
    }

    /**
     * Create UNIQUE constraint with conventional naming
     *
     * @param TableName $table Table name
     * @param ColumnName ...$columns Column names
     */
    public static function unique(TableName $table, ColumnName ...$columns): self
    {
        $columnNames = array_map(fn($col) => $col->value, $columns);
        $name = 'uq_' . $table->value . '_' . implode('_', $columnNames);

        return self::fromString($name);
    }

    /**
     * Create FOREIGN KEY constraint with conventional naming
     *
     * @param TableName $table Table name
     * @param ColumnName $column Column name
     * @param TableName $referencedTable Referenced table
     */
    public static function foreignKey(
        TableName $table,
        ColumnName $column,
        TableName $referencedTable
    ): self {
        $name = 'fk_' . $table->value . '_' . $column->value . '_' . $referencedTable->value;

        return self::fromString($name);
    }

    /**
     * Create CHECK constraint with conventional naming
     *
     * @param TableName $table Table name
     * @param string $checkName Descriptive check name (e.g., 'positive_amount')
     */
    public static function check(TableName $table, string $checkName): self
    {
        $name = 'chk_' . $table->value . '_' . $checkName;

        return self::fromString($name);
    }

    /**
     * Get constraint name as string
     */
    public function toString(): string
    {
        return $this->value;
    }

    /**
     * Get quoted constraint name for SQL
     *
     * Note: PRIMARY KEY, UNIQUE, CHECK are SQL keywords and should not be quoted
     *
     * @param string $platform Database platform (mysql, postgresql, sqlite)
     */
    public function quoted(string $platform = 'mysql'): string
    {
        // Don't quote SQL keyword constraints
        if ($this->isKeywordConstraint()) {
            return $this->value;
        }

        return match (strtolower($platform)) {
            'postgresql', 'postgres', 'pgsql' => '"' . $this->value . '"',
            'sqlite' => '"' . $this->value . '"',
            default => '`' . $this->value . '`', // MySQL default
        };
    }

    /**
     * Check if constraint name equals another
     *
     * Comparison is case-insensitive
     */
    public function equals(self $other): bool
    {
        return strcasecmp($this->value, $other->value) === 0;
    }

    /**
     * Check if constraint name matches a pattern
     *
     * Pattern supports wildcards (*) for matching
     * Example: 'fk_*' matches all foreign key constraints
     */
    public function matches(string $pattern): bool
    {
        // Replace * wildcards with placeholder, quote the pattern, then restore wildcards
        $placeholder = '___WILDCARD___';
        $pattern = str_replace('*', $placeholder, $pattern);
        $regex = '/^' . preg_quote($pattern, '/') . '$/i';
        $regex = str_replace($placeholder, '.*', $regex);

        return preg_match($regex, $this->value) === 1;
    }

    /**
     * Check if constraint name is a reserved SQL keyword
     */
    public function isReservedKeyword(): bool
    {
        $validator = new DatabaseIdentifierValidator();
        return $validator->isReservedKeyword($this->value);
    }

    /**
     * Convert to lowercase
     */
    public function toLower(): string
    {
        return strtolower($this->value);
    }

    /**
     * Check if constraint name has a prefix
     */
    public function hasPrefix(string $prefix): bool
    {
        return str_starts_with($this->value, $prefix);
    }

    /**
     * Check if constraint name has a suffix
     */
    public function hasSuffix(string $suffix): bool
    {
        return str_ends_with($this->value, $suffix);
    }

    /**
     * Check if this is PRIMARY KEY constraint
     */
    public function isPrimaryKey(): bool
    {
        return strcasecmp($this->value, 'PRIMARY KEY') === 0
            || strcasecmp($this->value, 'PRIMARY') === 0;
    }

    /**
     * Check if this is a FOREIGN KEY constraint
     */
    public function isForeignKey(): bool
    {
        return $this->hasPrefix('fk_')
            || strcasecmp($this->value, 'FOREIGN KEY') === 0;
    }

    /**
     * Check if this is a UNIQUE constraint
     */
    public function isUnique(): bool
    {
        return $this->hasPrefix('uq_')
            || $this->hasPrefix('unique_')
            || strcasecmp($this->value, 'UNIQUE') === 0;
    }

    /**
     * Check if this is a CHECK constraint
     */
    public function isCheck(): bool
    {
        return $this->hasPrefix('chk_')
            || $this->hasPrefix('check_')
            || strcasecmp($this->value, 'CHECK') === 0;
    }

    /**
     * Check if this is a DEFAULT constraint
     */
    public function isDefault(): bool
    {
        return $this->hasPrefix('df_')
            || $this->hasPrefix('default_')
            || strcasecmp($this->value, 'DEFAULT') === 0;
    }

    /**
     * Check if constraint is a SQL keyword constraint
     *
     * Keyword constraints: PRIMARY KEY, UNIQUE, CHECK, FOREIGN KEY, DEFAULT
     */
    public function isKeywordConstraint(): bool
    {
        $keywords = ['PRIMARY KEY', 'UNIQUE', 'CHECK', 'FOREIGN KEY', 'DEFAULT'];

        foreach ($keywords as $keyword) {
            if (strcasecmp($this->value, $keyword) === 0) {
                return true;
            }
        }

        return false;
    }

    /**
     * Get constraint type
     *
     * Returns: 'primary_key', 'foreign_key', 'unique', 'check', 'default', or 'custom'
     */
    public function getType(): string
    {
        if ($this->isPrimaryKey()) {
            return 'primary_key';
        }
        if ($this->isForeignKey()) {
            return 'foreign_key';
        }
        if ($this->isUnique()) {
            return 'unique';
        }
        if ($this->isCheck()) {
            return 'check';
        }
        if ($this->isDefault()) {
            return 'default';
        }

        return 'custom';
    }

    /**
     * Magic method for string conversion
     */
    public function __toString(): string
    {
        return $this->value;
    }
}
