<?php

declare(strict_types=1);

namespace App\Domain\Database\Services;

use App\Domain\Database\Models\MysqlDatabase;
use App\Domain\Database\Models\MysqlUser;
use Illuminate\Support\Facades\DB;

/**
 * MySQL ↔ MariaDB engine geçişlerinde sorun çıkarabilecek 20+ yapıyı
 * information_schema + DB queries üzerinden tarar.
 *
 * Her issue:
 *  - type      string  Kategori (json_column, sequence, vb.)
 *  - severity  ok|warn|block
 *  - message   Açıklama
 *  - auto_fix  null veya {sql: string, description: string, irreversible: bool}
 *
 * %100 garanti vermez (engine farkları kaçınılmaz) ama temel sorunları yakalar
 * ve mümkün olanlara otomatik düzeltme SQL'i önerir.
 */
final class EngineCompatibilityScanner
{
    /**
     * @return array{compatible: bool, issues: array<int, array<string, mixed>>, severity: string, summary: array<string, int>}
     */
    public function scan(MysqlDatabase $db, string $targetEngine, string $targetVersion): array
    {
        $issues = [];

        // Core checks (Faz C)
        $issues = array_merge($issues, $this->scanAuthPlugin($db, $targetEngine));
        $issues = array_merge($issues, $this->scanJsonColumns($db, $targetEngine));
        $issues = array_merge($issues, $this->scanGeneratedColumns($db));
        $issues = array_merge($issues, $this->scanSequences($db, $targetEngine));
        $issues = array_merge($issues, $this->scanStorageEngines($db, $targetEngine));

        // Genişletilmiş checks (önceki turda)
        $issues = array_merge($issues, $this->scanCheckConstraints($db, $targetEngine, $targetVersion));
        $issues = array_merge($issues, $this->scanEncryption($db, $targetEngine));
        $issues = array_merge($issues, $this->scanLargeRows($db));
        $issues = array_merge($issues, $this->scanViewDefiners($db));
        $issues = array_merge($issues, $this->scanRoutines($db, $targetEngine));

        // %100'e yaklaştırma — yeni 11 check
        $issues = array_merge($issues, $this->scanCharsetCollation($db, $targetEngine, $targetVersion));
        $issues = array_merge($issues, $this->scanForeignKeys($db));
        $issues = array_merge($issues, $this->scanFulltextIndexes($db, $targetEngine));
        $issues = array_merge($issues, $this->scanSpatialColumns($db));
        $issues = array_merge($issues, $this->scanEvents($db));
        $issues = array_merge($issues, $this->scanTriggers($db));
        $issues = array_merge($issues, $this->scanDeprecatedTypes($db, $targetEngine, $targetVersion));
        $issues = array_merge($issues, $this->scanReservedWordIdentifiers($db, $targetEngine, $targetVersion));
        $issues = array_merge($issues, $this->scanDefaultExpressions($db, $targetEngine, $targetVersion));
        $issues = array_merge($issues, $this->scanPartitions($db));
        $issues = array_merge($issues, $this->scanInvisibleColumns($db, $targetEngine, $targetVersion));

        $severity = $this->aggregateSeverity($issues);
        $summary = $this->summarize($issues);

        return [
            'compatible' => $severity !== 'block',
            'issues' => $issues,
            'severity' => $severity,
            'summary' => $summary,
        ];
    }

    /** @return array<int, array<string, mixed>> */
    private function scanAuthPlugin(MysqlDatabase $db, string $targetEngine): array
    {
        $issues = [];
        if ($targetEngine !== 'mariadb') {
            return $issues;
        }

        $users = MysqlUser::where('account_id', $db->account_id)
            ->where('auth_plugin', 'caching_sha2_password')
            ->pluck('real_user_name');

        foreach ($users as $userName) {
            $safe = $this->quoteIdent($userName);
            $issues[] = [
                'type' => 'auth_plugin',
                'severity' => 'warn',
                'message' => "Kullanıcı '{$userName}' caching_sha2_password kullanır — MariaDB desteklemez. Otomatik düzeltme şifreyi mysql_native_password'a çevirir (şifre sıfırlama gerekebilir).",
                'auto_fix' => [
                    'sql' => "ALTER USER {$safe} IDENTIFIED WITH 'mysql_native_password' BY '" . $this->randomPassword() . "'",
                    'description' => "Auth plugin'i mysql_native_password'a değiştir + yeni şifre üret (admin'e bildirilir)",
                    'irreversible' => true,
                ],
            ];
        }

        return $issues;
    }

    /** @return array<int, array<string, mixed>> */
    private function scanJsonColumns(MysqlDatabase $db, string $targetEngine): array
    {
        $issues = [];
        try {
            $cols = DB::select(
                'SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND DATA_TYPE = ?',
                [$db->real_db_name, 'json']
            );
            foreach ($cols as $c) {
                $issues[] = [
                    'type' => 'json_column',
                    'severity' => 'warn',
                    'message' => "JSON kolonu: {$c->TABLE_NAME}.{$c->COLUMN_NAME} — MariaDB JSON = LONGTEXT alias, MySQL JSON native. JSON_EXTRACT/-> operatörleri her ikisinde de çalışır ama validation farkı vardır.",
                    'auto_fix' => null,
                ];
            }
        } catch (\Throwable) {
        }

        return $issues;
    }

    /** @return array<int, array<string, mixed>> */
    private function scanGeneratedColumns(MysqlDatabase $db): array
    {
        $issues = [];
        try {
            $cols = DB::select(
                "SELECT TABLE_NAME, COLUMN_NAME, EXTRA FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND GENERATION_EXPRESSION IS NOT NULL AND GENERATION_EXPRESSION != ''",
                [$db->real_db_name]
            );
            foreach ($cols as $c) {
                $issues[] = [
                    'type' => 'generated_column',
                    'severity' => 'warn',
                    'message' => "Generated column: {$c->TABLE_NAME}.{$c->COLUMN_NAME} (EXTRA={$c->EXTRA}) — MariaDB PERSISTENT/VIRTUAL, MySQL STORED/VIRTUAL keyword'leri farklı. Schema yeniden parse edilir, syntax otomatik dönüşür ama edge case'lerde fail edebilir.",
                    'auto_fix' => null,
                ];
            }
        } catch (\Throwable) {
        }

        return $issues;
    }

    /** @return array<int, array<string, mixed>> */
    private function scanSequences(MysqlDatabase $db, string $targetEngine): array
    {
        $issues = [];
        if ($targetEngine !== 'mysql') {
            return $issues;
        }

        try {
            $seqs = DB::select(
                "SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = 'SEQUENCE'",
                [$db->real_db_name]
            );

            foreach ($seqs as $s) {
                $safeDb = $this->quoteIdent($db->real_db_name);
                $safeName = $this->quoteIdent($s->TABLE_NAME);
                $issues[] = [
                    'type' => 'sequence',
                    'severity' => 'block',
                    'message' => "Sequence '{$s->TABLE_NAME}' — MariaDB özelliği, MySQL desteklemez. Otomatik düzeltme sequence'i AUTO_INCREMENT'li bir tabloya dönüştürür.",
                    'auto_fix' => [
                        'sql' => "CREATE TABLE {$safeDb}.{$safeName}_table (next_val BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDB AS SELECT NEXTVAL({$safeDb}.{$safeName}) AS next_val; DROP SEQUENCE {$safeDb}.{$safeName};",
                        'description' => "Sequence → AUTO_INCREMENT tablo. Uygulama kodunda NEXTVAL() yerine INSERT/LAST_INSERT_ID() kullanmaya geçmelisin.",
                        'irreversible' => true,
                    ],
                ];
            }
        } catch (\Throwable) {
        }

        return $issues;
    }

    /** @return array<int, array<string, mixed>> */
    private function scanStorageEngines(MysqlDatabase $db, string $targetEngine): array
    {
        $issues = [];
        $mariaOnly = ['Aria', 'TokuDB', 'MyRocks', 'Spider', 'OQGRAPH', 'Connect', 'Sphinx', 'Mroonga'];

        try {
            $tables = DB::select(
                "SELECT TABLE_NAME, ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND ENGINE IS NOT NULL AND ENGINE NOT IN ('InnoDB', 'MyISAM', 'MEMORY', 'CSV', 'ARCHIVE')",
                [$db->real_db_name]
            );

            foreach ($tables as $t) {
                $isMariaOnly = in_array($t->ENGINE, $mariaOnly, true);
                $severity = ($isMariaOnly && $targetEngine === 'mysql') ? 'block' : 'warn';
                $safeDb = $this->quoteIdent($db->real_db_name);
                $safeName = $this->quoteIdent($t->TABLE_NAME);

                $issues[] = [
                    'type' => 'storage_engine',
                    'severity' => $severity,
                    'message' => "Storage engine '{$t->ENGINE}' kullanılıyor: {$t->TABLE_NAME}" . ($isMariaOnly ? ' — MariaDB-only engine, hedef MySQL destekleyemez' : ' — hedef engine tarafından desteklendiğini doğrula'),
                    'auto_fix' => [
                        'sql' => "ALTER TABLE {$safeDb}.{$safeName} ENGINE=InnoDB",
                        'description' => "Tabloyu InnoDB'ye dönüştür (hem MariaDB hem MySQL'de standart engine)",
                        'irreversible' => false,
                    ],
                ];
            }
        } catch (\Throwable) {
        }

        return $issues;
    }

    /** @return array<int, array<string, mixed>> */
    private function scanCheckConstraints(MysqlDatabase $db, string $targetEngine, string $targetVersion): array
    {
        $issues = [];
        try {
            $rows = DB::select(
                'SELECT TABLE_NAME, CONSTRAINT_NAME FROM information_schema.TABLE_CONSTRAINTS
                 WHERE TABLE_SCHEMA = ? AND CONSTRAINT_TYPE = ?',
                [$db->real_db_name, 'CHECK']
            );
            foreach ($rows as $c) {
                $issues[] = [
                    'type' => 'check_constraint',
                    'severity' => 'warn',
                    'message' => "CHECK constraint: {$c->TABLE_NAME}.{$c->CONSTRAINT_NAME} — MySQL 8.0.16'dan önce parsed but ignored, MariaDB 10.2'den beri enforced. Geçişte semantik fark olabilir.",
                    'auto_fix' => null,
                ];
            }
        } catch (\Throwable) {
        }

        return $issues;
    }

    /** @return array<int, array<string, mixed>> */
    private function scanEncryption(MysqlDatabase $db, string $targetEngine): array
    {
        $issues = [];
        try {
            $rows = DB::select(
                "SELECT TABLE_NAME, CREATE_OPTIONS FROM information_schema.TABLES
                 WHERE TABLE_SCHEMA = ? AND CREATE_OPTIONS LIKE '%ENCRYPTION%'",
                [$db->real_db_name]
            );
            foreach ($rows as $t) {
                $safeDb = $this->quoteIdent($db->real_db_name);
                $safeName = $this->quoteIdent($t->TABLE_NAME);
                $issues[] = [
                    'type' => 'encryption',
                    'severity' => 'block',
                    'message' => "Tablo {$t->TABLE_NAME} TDE (transparent data encryption) kullanıyor — engine değişiminde anahtar yönetimi bozulabilir. Otomatik düzeltme şifrelemeyi kapatır (veri okunur kalır).",
                    'auto_fix' => [
                        'sql' => "ALTER TABLE {$safeDb}.{$safeName} ENCRYPTION='N'",
                        'description' => "Tablo TDE şifrelemesini kapat. Veri okunur kalır, dosya seviyesinde şifre kalkar. (Compliance gereksinimi varsa dikkat!)",
                        'irreversible' => false,
                    ],
                ];
            }
        } catch (\Throwable) {
        }

        return $issues;
    }

    /** @return array<int, array<string, mixed>> */
    private function scanLargeRows(MysqlDatabase $db): array
    {
        $issues = [];
        try {
            $rows = DB::select(
                "SELECT TABLE_NAME, ROW_FORMAT FROM information_schema.TABLES
                 WHERE TABLE_SCHEMA = ? AND ROW_FORMAT IN ('Compact', 'Redundant')",
                [$db->real_db_name]
            );
            foreach ($rows as $t) {
                $safeDb = $this->quoteIdent($db->real_db_name);
                $safeName = $this->quoteIdent($t->TABLE_NAME);
                $issues[] = [
                    'type' => 'row_format',
                    'severity' => 'warn',
                    'message' => "Tablo {$t->TABLE_NAME} ROW_FORMAT={$t->ROW_FORMAT} — yeni sürümlerde DYNAMIC default. Geniş TEXT/BLOB içerirse migration sonrası INSERT'lerde 'Row size too large' hatası alabilirsiniz.",
                    'auto_fix' => [
                        'sql' => "ALTER TABLE {$safeDb}.{$safeName} ROW_FORMAT=DYNAMIC",
                        'description' => "ROW_FORMAT'ı DYNAMIC'e dönüştür (16K page + off-page TEXT/BLOB storage)",
                        'irreversible' => false,
                    ],
                ];
            }
        } catch (\Throwable) {
        }

        return $issues;
    }

    /** @return array<int, array<string, mixed>> */
    private function scanViewDefiners(MysqlDatabase $db): array
    {
        $issues = [];
        try {
            $rows = DB::select(
                'SELECT TABLE_NAME, DEFINER, SECURITY_TYPE FROM information_schema.VIEWS WHERE TABLE_SCHEMA = ?',
                [$db->real_db_name]
            );
            foreach ($rows as $v) {
                $definer = (string) ($v->DEFINER ?? '');
                if ($definer !== '' && $definer !== 'root@localhost' && $definer !== 'CURRENT_USER@%') {
                    $safeDb = $this->quoteIdent($db->real_db_name);
                    $safeName = $this->quoteIdent($v->TABLE_NAME);
                    $issues[] = [
                        'type' => 'view_definer',
                        'severity' => 'warn',
                        'message' => "View {$v->TABLE_NAME} DEFINER='{$definer}' ({$v->SECURITY_TYPE}) — engine geçişinde user kalmazsa view çalışmaz.",
                        'auto_fix' => [
                            'sql' => "ALTER DEFINER=CURRENT_USER VIEW {$safeDb}.{$safeName} AS (SELECT * FROM {$safeDb}.{$safeName})",
                            'description' => "DEFINER'ı CURRENT_USER'a değiştir (root + INVOKER security_type önerilir, manuel set et)",
                            'irreversible' => false,
                        ],
                    ];
                }
            }
        } catch (\Throwable) {
        }

        return $issues;
    }

    /** @return array<int, array<string, mixed>> */
    private function scanRoutines(MysqlDatabase $db, string $targetEngine): array
    {
        $issues = [];
        try {
            $rows = DB::select(
                'SELECT ROUTINE_NAME, ROUTINE_TYPE, EXTERNAL_LANGUAGE FROM information_schema.ROUTINES WHERE ROUTINE_SCHEMA = ?',
                [$db->real_db_name]
            );
            foreach ($rows as $r) {
                if (! empty($r->EXTERNAL_LANGUAGE) && strtoupper($r->EXTERNAL_LANGUAGE) !== 'SQL') {
                    $issues[] = [
                        'type' => 'routine_language',
                        'severity' => 'block',
                        'message' => "{$r->ROUTINE_TYPE} {$r->ROUTINE_NAME} external language={$r->EXTERNAL_LANGUAGE} — hedef engine bu dili desteklemeyebilir (manuel SQL/PSM'e port et).",
                        'auto_fix' => null,
                    ];
                }
            }
            if (count($rows) > 0) {
                $issues[] = [
                    'type' => 'routine_count',
                    'severity' => 'warn',
                    'message' => count($rows) . ' adet stored routine var — engine geçişinden sonra her birini manuel test edin (SHOW WARNINGS).',
                    'auto_fix' => null,
                ];
            }
        } catch (\Throwable) {
        }

        return $issues;
    }

    /** @return array<int, array<string, mixed>> */
    private function scanCharsetCollation(MysqlDatabase $db, string $targetEngine, string $targetVersion): array
    {
        $issues = [];
        try {
            // utf8 → utf8mb3 alias (deprecated MySQL 8.0+), utf8mb4_general_ci default farkları
            $cols = DB::select(
                "SELECT TABLE_NAME, COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAME
                 FROM information_schema.COLUMNS
                 WHERE TABLE_SCHEMA = ? AND CHARACTER_SET_NAME IN ('utf8', 'utf8mb3')",
                [$db->real_db_name]
            );
            $tableSet = [];
            foreach ($cols as $c) {
                $tableSet[$c->TABLE_NAME] = true;
            }
            foreach (array_keys($tableSet) as $tableName) {
                $safeDb = $this->quoteIdent($db->real_db_name);
                $safeName = $this->quoteIdent($tableName);
                $issues[] = [
                    'type' => 'charset_legacy',
                    'severity' => 'warn',
                    'message' => "Tablo {$tableName} utf8/utf8mb3 charset kullanıyor — MySQL 8.0+ deprecated, utf8mb4 önerilir (emoji + 4-byte char desteği).",
                    'auto_fix' => [
                        'sql' => "ALTER TABLE {$safeDb}.{$safeName} CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",
                        'description' => 'utf8mb4_unicode_ci charset/collation\'a dönüştür (eski utf8 = utf8mb3 alias)',
                        'irreversible' => false,
                    ],
                ];
            }

            // utf8mb4_0900_* collations (MySQL 8.0 only — MariaDB desteklemez)
            if ($targetEngine === 'mariadb') {
                $badCol = DB::select(
                    "SELECT DISTINCT TABLE_NAME, COLLATION_NAME FROM information_schema.COLUMNS
                     WHERE TABLE_SCHEMA = ? AND COLLATION_NAME LIKE 'utf8mb4_0900_%'",
                    [$db->real_db_name]
                );
                foreach ($badCol as $c) {
                    $safeDb = $this->quoteIdent($db->real_db_name);
                    $safeName = $this->quoteIdent($c->TABLE_NAME);
                    $issues[] = [
                        'type' => 'mysql_only_collation',
                        'severity' => 'block',
                        'message' => "Tablo {$c->TABLE_NAME} {$c->COLLATION_NAME} kullanıyor — MariaDB bu collation'ı desteklemez (MySQL 8.0+ Unicode 9.0 implementasyonu).",
                        'auto_fix' => [
                            'sql' => "ALTER TABLE {$safeDb}.{$safeName} CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",
                            'description' => 'utf8mb4_unicode_ci\'ye dönüştür (her iki engine destekler)',
                            'irreversible' => true,
                        ],
                    ];
                }
            }
        } catch (\Throwable) {
        }

        return $issues;
    }

    /** @return array<int, array<string, mixed>> */
    private function scanForeignKeys(MysqlDatabase $db): array
    {
        $issues = [];
        try {
            // FK constraints var ama referenced table farklı engine ise problem
            $rows = DB::select(
                "SELECT k.TABLE_NAME, k.REFERENCED_TABLE_NAME, t1.ENGINE AS local_engine, t2.ENGINE AS ref_engine
                 FROM information_schema.KEY_COLUMN_USAGE k
                 JOIN information_schema.TABLES t1 ON t1.TABLE_SCHEMA=k.TABLE_SCHEMA AND t1.TABLE_NAME=k.TABLE_NAME
                 LEFT JOIN information_schema.TABLES t2 ON t2.TABLE_SCHEMA=k.REFERENCED_TABLE_SCHEMA AND t2.TABLE_NAME=k.REFERENCED_TABLE_NAME
                 WHERE k.TABLE_SCHEMA = ? AND k.REFERENCED_TABLE_NAME IS NOT NULL",
                [$db->real_db_name]
            );
            $crossEngine = [];
            foreach ($rows as $r) {
                if ($r->local_engine !== $r->ref_engine && $r->ref_engine !== null) {
                    $crossEngine["{$r->TABLE_NAME}→{$r->REFERENCED_TABLE_NAME}"] = $r;
                }
            }
            foreach ($crossEngine as $key => $r) {
                $issues[] = [
                    'type' => 'fk_cross_engine',
                    'severity' => 'warn',
                    'message' => "FK {$key}: {$r->local_engine} → {$r->ref_engine} — farklı storage engine arası FK riskli, ALTER ENGINE sonrası FK enforcement değişebilir.",
                    'auto_fix' => null,
                ];
            }
        } catch (\Throwable) {
        }

        return $issues;
    }

    /** @return array<int, array<string, mixed>> */
    private function scanFulltextIndexes(MysqlDatabase $db, string $targetEngine): array
    {
        $issues = [];
        try {
            $rows = DB::select(
                "SELECT DISTINCT TABLE_NAME, INDEX_NAME FROM information_schema.STATISTICS
                 WHERE TABLE_SCHEMA = ? AND INDEX_TYPE = 'FULLTEXT'",
                [$db->real_db_name]
            );
            foreach ($rows as $r) {
                $issues[] = [
                    'type' => 'fulltext_index',
                    'severity' => 'warn',
                    'message' => "FULLTEXT index: {$r->TABLE_NAME}.{$r->INDEX_NAME} — parser implementations farklı (MariaDB Mroonga vs MySQL InnoDB ngram). Geçiş sonrası MATCH() sorgu sonuçları farklılaşabilir.",
                    'auto_fix' => null,
                ];
            }
        } catch (\Throwable) {
        }

        return $issues;
    }

    /** @return array<int, array<string, mixed>> */
    private function scanSpatialColumns(MysqlDatabase $db): array
    {
        $issues = [];
        try {
            $rows = DB::select(
                "SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE FROM information_schema.COLUMNS
                 WHERE TABLE_SCHEMA = ? AND DATA_TYPE IN ('geometry', 'point', 'linestring', 'polygon', 'multipoint', 'multilinestring', 'multipolygon', 'geometrycollection')",
                [$db->real_db_name]
            );
            foreach ($rows as $c) {
                $issues[] = [
                    'type' => 'spatial_column',
                    'severity' => 'warn',
                    'message' => "Spatial kolon: {$c->TABLE_NAME}.{$c->COLUMN_NAME} ({$c->DATA_TYPE}) — MySQL 8.0 SRID-aware, MariaDB değil. ST_* fonksiyon prefix'leri ve axis order farklı.",
                    'auto_fix' => null,
                ];
            }
        } catch (\Throwable) {
        }

        return $issues;
    }

    /** @return array<int, array<string, mixed>> */
    private function scanEvents(MysqlDatabase $db): array
    {
        $issues = [];
        try {
            $rows = DB::select(
                'SELECT EVENT_NAME, STATUS, DEFINER FROM information_schema.EVENTS WHERE EVENT_SCHEMA = ?',
                [$db->real_db_name]
            );
            foreach ($rows as $e) {
                $issues[] = [
                    'type' => 'event',
                    'severity' => 'warn',
                    'message' => "Event '{$e->EVENT_NAME}' (DEFINER={$e->DEFINER}, status={$e->STATUS}) — engine geçişinde event_scheduler default kapalı olabilir, etkinleştirmeyi unutmayın.",
                    'auto_fix' => [
                        'sql' => "SET GLOBAL event_scheduler = ON",
                        'description' => 'Event scheduler\'ı global aç (my.cnf\'e event_scheduler=ON eklenmesi kalıcı çözüm)',
                        'irreversible' => false,
                    ],
                ];
            }
        } catch (\Throwable) {
        }

        return $issues;
    }

    /** @return array<int, array<string, mixed>> */
    private function scanTriggers(MysqlDatabase $db): array
    {
        $issues = [];
        try {
            $rows = DB::select(
                'SELECT TRIGGER_NAME, EVENT_OBJECT_TABLE, DEFINER FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA = ?',
                [$db->real_db_name]
            );
            foreach ($rows as $t) {
                $definer = (string) ($t->DEFINER ?? '');
                if ($definer !== '' && $definer !== 'root@localhost') {
                    $issues[] = [
                        'type' => 'trigger_definer',
                        'severity' => 'warn',
                        'message' => "Trigger '{$t->TRIGGER_NAME}' on {$t->EVENT_OBJECT_TABLE} DEFINER={$definer} — user yoksa trigger yetkisi başarısız olur, geçiş sonrası test edin.",
                        'auto_fix' => null,
                    ];
                }
            }
        } catch (\Throwable) {
        }

        return $issues;
    }

    /** @return array<int, array<string, mixed>> */
    private function scanDeprecatedTypes(MysqlDatabase $db, string $targetEngine, string $targetVersion): array
    {
        $issues = [];
        $deprecated = [
            'year(2)' => 'YEAR(4) kullan',
            'time without microsecond precision' => 'TIME(6) kullan',
            'datetime without microsecond precision' => 'DATETIME(6) kullan',
        ];
        try {
            // YEAR(2) deprecated since MySQL 5.7
            $rows = DB::select(
                "SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE FROM information_schema.COLUMNS
                 WHERE TABLE_SCHEMA = ? AND COLUMN_TYPE = 'year(2)'",
                [$db->real_db_name]
            );
            foreach ($rows as $c) {
                $safeDb = $this->quoteIdent($db->real_db_name);
                $safeName = $this->quoteIdent($c->TABLE_NAME);
                $safeCol = $this->quoteIdent($c->COLUMN_NAME);
                $issues[] = [
                    'type' => 'deprecated_type',
                    'severity' => 'block',
                    'message' => "Deprecated type: {$c->TABLE_NAME}.{$c->COLUMN_NAME} YEAR(2) — yeni sürümler desteklemez. YEAR(4)'e dönüştürülmeli.",
                    'auto_fix' => [
                        'sql' => "ALTER TABLE {$safeDb}.{$safeName} MODIFY {$safeCol} YEAR",
                        'description' => 'YEAR(2) → YEAR(4) — değerler 00-69 → 2000-2069, 70-99 → 1970-1999',
                        'irreversible' => true,
                    ],
                ];
            }
        } catch (\Throwable) {
        }

        return $issues;
    }

    /** @return array<int, array<string, mixed>> */
    private function scanReservedWordIdentifiers(MysqlDatabase $db, string $targetEngine, string $targetVersion): array
    {
        $issues = [];
        // MySQL 8.0/8.4'te eklenmiş yeni reserved words — MariaDB'de henüz değil
        $newReserved = ['RANK', 'DENSE_RANK', 'CUME_DIST', 'PERCENT_RANK', 'ROW_NUMBER',
            'LEAD', 'LAG', 'FIRST_VALUE', 'LAST_VALUE', 'NTH_VALUE', 'NTILE',
            'OVER', 'WINDOW', 'RECURSIVE', 'GROUPING', 'EXCEPT', 'INTERSECT',
            'JSON_TABLE', 'LATERAL', 'OF', 'SYSTEM', 'EMPTY', 'ARRAY'];

        try {
            $cols = DB::select(
                'SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ?',
                [$db->real_db_name]
            );
            foreach ($cols as $c) {
                if (in_array(strtoupper($c->COLUMN_NAME), $newReserved, true)) {
                    $issues[] = [
                        'type' => 'reserved_word',
                        'severity' => 'warn',
                        'message' => "Kolon `{$c->TABLE_NAME}`.`{$c->COLUMN_NAME}` MySQL 8.0+ reserved word — sorgu içinde backtick kullanılmazsa syntax hatası verir.",
                        'auto_fix' => null,
                    ];
                }
            }
        } catch (\Throwable) {
        }

        return $issues;
    }

    /** @return array<int, array<string, mixed>> */
    private function scanDefaultExpressions(MysqlDatabase $db, string $targetEngine, string $targetVersion): array
    {
        $issues = [];
        try {
            // EXPRESSION default (MySQL 8.0.13+ / MariaDB 10.2+) — sürüm farklı handle eder
            $rows = DB::select(
                "SELECT TABLE_NAME, COLUMN_NAME, COLUMN_DEFAULT, EXTRA FROM information_schema.COLUMNS
                 WHERE TABLE_SCHEMA = ? AND EXTRA LIKE '%DEFAULT_GENERATED%'",
                [$db->real_db_name]
            );
            foreach ($rows as $c) {
                $issues[] = [
                    'type' => 'default_expression',
                    'severity' => 'warn',
                    'message' => "Expression default: {$c->TABLE_NAME}.{$c->COLUMN_NAME} (DEFAULT={$c->COLUMN_DEFAULT}) — MariaDB/MySQL syntax farkı olabilir, schema parse'te uyarı çıkabilir.",
                    'auto_fix' => null,
                ];
            }
        } catch (\Throwable) {
        }

        return $issues;
    }

    /** @return array<int, array<string, mixed>> */
    private function scanPartitions(MysqlDatabase $db): array
    {
        $issues = [];
        try {
            $rows = DB::select(
                "SELECT DISTINCT TABLE_NAME, PARTITION_METHOD FROM information_schema.PARTITIONS
                 WHERE TABLE_SCHEMA = ? AND PARTITION_NAME IS NOT NULL",
                [$db->real_db_name]
            );
            foreach ($rows as $p) {
                $issues[] = [
                    'type' => 'partition',
                    'severity' => 'warn',
                    'message' => "Partitioned tablo: {$p->TABLE_NAME} (method={$p->PARTITION_METHOD}) — MySQL 8.0 partition engine'i KAlDIRILDI, sadece InnoDB native partition. MariaDB hala ayrı engine destekliyor. Geçiş sonrası test edin.",
                    'auto_fix' => null,
                ];
            }
        } catch (\Throwable) {
        }

        return $issues;
    }

    /** @return array<int, array<string, mixed>> */
    private function scanInvisibleColumns(MysqlDatabase $db, string $targetEngine, string $targetVersion): array
    {
        $issues = [];
        try {
            // MySQL 8.0.23+ INVISIBLE columns, MariaDB INVISIBLE 10.3+ farklı syntax
            $rows = DB::select(
                "SELECT TABLE_NAME, COLUMN_NAME, EXTRA FROM information_schema.COLUMNS
                 WHERE TABLE_SCHEMA = ? AND EXTRA LIKE '%INVISIBLE%'",
                [$db->real_db_name]
            );
            foreach ($rows as $c) {
                $issues[] = [
                    'type' => 'invisible_column',
                    'severity' => 'warn',
                    'message' => "INVISIBLE kolon: {$c->TABLE_NAME}.{$c->COLUMN_NAME} — MySQL 8.0.23+ ve MariaDB 10.3+ destekler ama syntax farkı vardır, dump/restore'da kontrol edin.",
                    'auto_fix' => null,
                ];
            }
        } catch (\Throwable) {
        }

        return $issues;
    }

    /** @param array<int, array<string, mixed>> $issues */
    private function aggregateSeverity(array $issues): string
    {
        if (empty($issues)) {
            return 'ok';
        }
        foreach ($issues as $issue) {
            if (($issue['severity'] ?? '') === 'block') {
                return 'block';
            }
        }

        return 'warn';
    }

    /**
     * @param array<int, array<string, mixed>> $issues
     * @return array{total: int, block: int, warn: int, auto_fixable: int}
     */
    private function summarize(array $issues): array
    {
        $summary = ['total' => count($issues), 'block' => 0, 'warn' => 0, 'auto_fixable' => 0];
        foreach ($issues as $issue) {
            $sev = $issue['severity'] ?? 'warn';
            if ($sev === 'block') {
                $summary['block']++;
            } elseif ($sev === 'warn') {
                $summary['warn']++;
            }
            if (! empty($issue['auto_fix']['sql'])) {
                $summary['auto_fixable']++;
            }
        }

        return $summary;
    }

    private function quoteIdent(string $name): string
    {
        // Backtick-quote, internal backtick'leri escape et
        return '`' . str_replace('`', '``', $name) . '`';
    }

    private function randomPassword(): string
    {
        return bin2hex(random_bytes(12));
    }

    /**
     * @return array{engine: string, version: string, label: string}
     */
    public static function detectServerEngine(): array
    {
        try {
            $row = DB::selectOne('SELECT VERSION() AS v');
            $v = $row?->v ?? '';
            $engine = str_contains(strtolower($v), 'mariadb') ? 'mariadb' : 'mysql';
            $version = preg_match('/(\d+\.\d+)/', $v, $m) ? $m[1] : 'unknown';
            $label = $engine === 'mariadb' ? "MariaDB {$version}" : "MySQL {$version}";

            return ['engine' => $engine, 'version' => $version, 'label' => $label];
        } catch (\Throwable) {
            return ['engine' => 'mariadb', 'version' => 'unknown', 'label' => 'MariaDB (unknown)'];
        }
    }
}
