<?php

declare(strict_types=1);

namespace App\Framework\Database\Indexing;

use App\Framework\Database\Indexing\ValueObjects\IndexRecommendation;
use App\Framework\Database\PdoConnection;

/**
 * Generates database migration files for index optimizations
 *
 * Creates both UP and DOWN migrations for safe index management
 */
final readonly class IndexMigrationGenerator
{
    private const MIGRATION_TEMPLATE = <<<'PHP'
<?php

declare(strict_types=1);

use App\Framework\Database\Migration\Migration;
use App\Framework\Database\Schema\Schema;

final class {CLASS_NAME} extends Migration
{
    public function up(Schema $schema): void
    {
        // {DESCRIPTION}
        $schema->table('{TABLE_NAME}', function ($table) {
{UP_STATEMENTS}
        });
    }

    public function down(Schema $schema): void
    {
        $schema->table('{TABLE_NAME}', function ($table) {
{DOWN_STATEMENTS}
        });
    }
}

PHP;

    public function __construct(
        private PdoConnection $connection
    ) {}

    /**
     * Generate migration file for adding recommended indexes
     *
     * @param array<IndexRecommendation> $recommendations
     */
    public function generateAddIndexMigration(array $recommendations, string $tableName): string
    {
        if (empty($recommendations)) {
            throw new \InvalidArgumentException('No index recommendations provided');
        }

        $className = $this->generateClassName('AddIndexesTo' . ucfirst($tableName));
        $upStatements = [];
        $downStatements = [];

        foreach ($recommendations as $recommendation) {
            $indexName = $recommendation->getIndexName()->toString();
            $columns = $recommendation->columns;

            $upStatements[] = $this->generateAddIndexStatement($indexName, $columns, $recommendation->indexType->value);
            $downStatements[] = $this->generateDropIndexStatement($indexName);
        }

        $description = 'Add optimized indexes based on query analysis';

        return $this->fillTemplate(
            className: $className,
            tableName: $tableName,
            description: $description,
            upStatements: $upStatements,
            downStatements: $downStatements
        );
    }

    /**
     * Generate migration file for removing unused indexes
     *
     * @param array<array{index_name: string, columns: array<string>}> $unusedIndexes
     */
    public function generateRemoveIndexMigration(array $unusedIndexes, string $tableName): string
    {
        if (empty($unusedIndexes)) {
            throw new \InvalidArgumentException('No unused indexes provided');
        }

        $className = $this->generateClassName('RemoveUnusedIndexesFrom' . ucfirst($tableName));
        $upStatements = [];
        $downStatements = [];

        foreach ($unusedIndexes as $index) {
            $indexName = $index['index_name'];
            $columns = $index['columns'];

            $upStatements[] = $this->generateDropIndexStatement($indexName);
            $downStatements[] = $this->generateAddIndexStatement($indexName, $columns, 'BTREE');
        }

        $description = 'Remove unused indexes identified by analysis';

        return $this->fillTemplate(
            className: $className,
            tableName: $tableName,
            description: $description,
            upStatements: $upStatements,
            downStatements: $downStatements
        );
    }

    /**
     * Generate comprehensive optimization migration (add + remove)
     *
     * @param array<IndexRecommendation> $toAdd
     * @param array<array{index_name: string, columns: array<string>}> $toRemove
     */
    public function generateOptimizationMigration(array $toAdd, array $toRemove, string $tableName): string
    {
        $className = $this->generateClassName('OptimizeIndexesFor' . ucfirst($tableName));
        $upStatements = [];
        $downStatements = [];

        // First, drop unused indexes
        foreach ($toRemove as $index) {
            $indexName = $index['index_name'];
            $columns = $index['columns'];

            $upStatements[] = $this->generateDropIndexStatement($indexName);
            $downStatements[] = $this->generateAddIndexStatement($indexName, $columns, 'BTREE');
        }

        // Then, add recommended indexes
        foreach ($toAdd as $recommendation) {
            $indexName = $recommendation->getIndexName()->toString();
            $columns = $recommendation->columns;

            $upStatements[] = $this->generateAddIndexStatement($indexName, $columns, $recommendation->indexType->value);
            $downStatements[] = $this->generateDropIndexStatement($indexName);
        }

        // Reverse down statements to maintain proper order
        $downStatements = array_reverse($downStatements);

        $description = 'Optimize indexes: remove unused, add recommended';

        return $this->fillTemplate(
            className: $className,
            tableName: $tableName,
            description: $description,
            upStatements: $upStatements,
            downStatements: $downStatements
        );
    }

    /**
     * Save migration file to disk
     */
    public function saveMigration(string $migrationContent, ?string $customPath = null): string
    {
        $timestamp = date('YmdHis');
        $className = $this->extractClassName($migrationContent);
        $snakeCaseName = $this->camelToSnake($className);

        $filename = "{$timestamp}_{$snakeCaseName}.php";
        $path = $customPath ?? __DIR__ . '/../../../migrations/' . $filename;

        // Ensure directory exists
        $directory = dirname($path);
        if (!is_dir($directory)) {
            mkdir($directory, 0755, true);
        }

        file_put_contents($path, $migrationContent);

        return $path;
    }

    /**
     * Generate migration class name with timestamp
     */
    private function generateClassName(string $baseName): string
    {
        return 'Migration' . date('YmdHis') . $baseName;
    }

    /**
     * Generate ADD INDEX statement
     */
    private function generateAddIndexStatement(string $indexName, array $columns, string $type = 'BTREE'): string
    {
        $columnsStr = implode(', ', array_map(fn($col) => "'{$col}'", $columns));

        return "\$table->index([{$columnsStr}], '{$indexName}', '{$type}');";
    }

    /**
     * Generate DROP INDEX statement
     */
    private function generateDropIndexStatement(string $indexName): string
    {
        return "\$table->dropIndex('{$indexName}');";
    }

    /**
     * Fill migration template with actual data
     */
    private function fillTemplate(
        string $className,
        string $tableName,
        string $description,
        array $upStatements,
        array $downStatements
    ): string {
        $upStatementsStr = implode("\n", array_map(
            fn($stmt) => "            {$stmt}",
            $upStatements
        ));

        $downStatementsStr = implode("\n", array_map(
            fn($stmt) => "            {$stmt}",
            $downStatements
        ));

        return str_replace(
            ['{CLASS_NAME}', '{TABLE_NAME}', '{DESCRIPTION}', '{UP_STATEMENTS}', '{DOWN_STATEMENTS}'],
            [$className, $tableName, $description, $upStatementsStr, $downStatementsStr],
            self::MIGRATION_TEMPLATE
        );
    }

    /**
     * Extract class name from migration content
     */
    private function extractClassName(string $content): string
    {
        if (preg_match('/final class (\w+) extends Migration/', $content, $matches)) {
            return $matches[1];
        }

        throw new \RuntimeException('Could not extract class name from migration');
    }

    /**
     * Convert camelCase to snake_case
     */
    private function camelToSnake(string $input): string
    {
        return strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $input));
    }

    /**
     * Validate migration content before saving
     */
    private function validateMigration(string $content): bool
    {
        // Basic validation
        if (!str_contains($content, 'extends Migration')) {
            throw new \RuntimeException('Migration must extend Migration class');
        }

        if (!str_contains($content, 'public function up(')) {
            throw new \RuntimeException('Migration must have up() method');
        }

        if (!str_contains($content, 'public function down(')) {
            throw new \RuntimeException('Migration must have down() method');
        }

        return true;
    }
}
