<?php

declare(strict_types=1);

namespace App\Framework\Database\Migration\ValueObjects;

use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\FrameworkException;

/**
 * Configuration for migration table structure and naming
 */
final readonly class MigrationTableConfig
{
    public function __construct(
        public string $tableName,
        public string $versionColumn = 'version',
        public string $descriptionColumn = 'description',
        public string $executedAtColumn = 'executed_at',
        public string $idColumn = 'id'
    ) {
        if (empty($tableName)) {
            throw FrameworkException::create(
                ErrorCode::VAL_BUSINESS_RULE_VIOLATION,
                'Migration table name cannot be empty'
            )->withData(['table_name' => $tableName]);
        }

        $this->validateColumnName($versionColumn, 'version');
        $this->validateColumnName($descriptionColumn, 'description');
        $this->validateColumnName($executedAtColumn, 'executed_at');
        $this->validateColumnName($idColumn, 'id');
    }

    public static function default(): self
    {
        return new self('migrations');
    }

    public static function withCustomTable(string $tableName): self
    {
        return new self($tableName);
    }

    /**
     * Get the full INSERT SQL for recording a migration
     */
    public function getInsertSql(): string
    {
        return "INSERT INTO {$this->tableName} ({$this->versionColumn}, {$this->descriptionColumn}, {$this->executedAtColumn}) VALUES (?, ?, ?)";
    }

    /**
     * Get the SELECT SQL for fetching applied versions
     */
    public function getVersionSelectSql(): string
    {
        return "SELECT {$this->versionColumn} FROM {$this->tableName} ORDER BY {$this->executedAtColumn}";
    }

    /**
     * Get the DELETE SQL for removing a migration record
     */
    public function getDeleteSql(): string
    {
        return "DELETE FROM {$this->tableName} WHERE {$this->versionColumn} = ?";
    }

    /**
     * Get the CREATE TABLE SQL for the given database driver
     */
    public function getCreateTableSql(string $driver): string
    {
        return match($driver) {
            'mysql' => "CREATE TABLE IF NOT EXISTS {$this->tableName} (
                {$this->idColumn} INT PRIMARY KEY AUTO_INCREMENT,
                {$this->versionColumn} VARCHAR(20) NOT NULL UNIQUE COMMENT 'Format: YYYY_MM_DD_HHMMSS',
                {$this->descriptionColumn} TEXT,
                {$this->executedAtColumn} TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_{$this->versionColumn} ({$this->versionColumn}),
                INDEX idx_{$this->executedAtColumn} ({$this->executedAtColumn})
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",

            'pgsql' => "CREATE TABLE IF NOT EXISTS {$this->tableName} (
                {$this->idColumn} SERIAL PRIMARY KEY,
                {$this->versionColumn} VARCHAR(20) NOT NULL UNIQUE,
                {$this->descriptionColumn} TEXT,
                {$this->executedAtColumn} TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
            )",

            'sqlite' => "CREATE TABLE IF NOT EXISTS {$this->tableName} (
                {$this->idColumn} INTEGER PRIMARY KEY AUTOINCREMENT,
                {$this->versionColumn} TEXT NOT NULL UNIQUE,
                {$this->descriptionColumn} TEXT,
                {$this->executedAtColumn} TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
            )",

            default => throw FrameworkException::create(
                ErrorCode::VAL_UNSUPPORTED_OPERATION,
                "Unsupported database driver for migrations"
            )->withData(['driver' => $driver])
        };
    }

    /**
     * Get PostgreSQL index creation SQL (separate from table creation)
     */
    public function getPostgreSqlIndexSql(): array
    {
        return [
            "CREATE INDEX IF NOT EXISTS idx_{$this->tableName}_{$this->versionColumn} ON {$this->tableName} ({$this->versionColumn})",
            "CREATE INDEX IF NOT EXISTS idx_{$this->tableName}_{$this->executedAtColumn} ON {$this->tableName} ({$this->executedAtColumn})",
        ];
    }

    private function validateColumnName(string $columnName, string $context): void
    {
        if (empty($columnName)) {
            throw FrameworkException::create(
                ErrorCode::VAL_BUSINESS_RULE_VIOLATION,
                "Migration {$context} column name cannot be empty"
            )->withData([
                'column_name' => $columnName,
                'context' => $context,
            ]);
        }

        // Basic SQL injection prevention
        if (! preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $columnName)) {
            throw FrameworkException::create(
                ErrorCode::VAL_BUSINESS_RULE_VIOLATION,
                "Invalid column name format for migration {$context}"
            )->withData([
                'column_name' => $columnName,
                'context' => $context,
            ]);
        }
    }
}
