"""
aicontract — DB Migration Guard
Geri dönüşsüz migration işlemlerini yakalar, onay zorunlu kılar.

Desteklenen frameworkler: Alembic, Django migrations, ham SQL dosyaları

Kullanım:
    from aicontract.migration_guard import MigrationGuard
    guard = MigrationGuard(connection_string="postgresql://...")
    result = guard.check_file("migrations/0023_remove_user_email.py")
    if result.is_dangerous:
        print(result.report())
        # CI'da exit(1) ile pipeline'ı durdur
"""

import re
import os
from dataclasses import dataclass, field
from typing import Optional
from datetime import datetime

# ruff: noqa: F401 (optional dependency)
try:
    import sqlalchemy  # noqa: F401
    from sqlalchemy import create_engine, text  # noqa: F401

    HAS_SQLALCHEMY = True
except ImportError:
    HAS_SQLALCHEMY = False


@dataclass
class DangerousOp:
    op_type: str  # DROP_COLUMN | DROP_TABLE | TRUNCATE | DATA_LOSS
    target: str  # tablo/kolon adı
    line: int
    row_count: Optional[int] = None  # gerçek satır sayısı (varsa)
    reversible: bool = False
    message: str = ""
    suggestion: str = ""


@dataclass
class MigrationCheckResult:
    file: str
    is_dangerous: bool
    operations: list[DangerousOp] = field(default_factory=list)
    danger_score: int = 0  # 0-100

    def report(self) -> str:
        lines = [f"\n{'=' * 60}"]
        lines.append(f"[aicontract/migration] {self.file}")
        lines.append(f"Tehlike skoru: {self.danger_score}/100")
        lines.append(f"{'=' * 60}")
        for op in self.operations:
            icon = "KRITIK" if not op.reversible else "UYARI"
            lines.append(f"\n[{icon}] {op.op_type} — {op.target} (satır {op.line})")
            lines.append(f"  {op.message}")
            if op.row_count is not None:
                lines.append(f"  Etkilenen satır: {op.row_count:,}")
            lines.append(f"  Öneri: {op.suggestion}")
        return "\n".join(lines)

    def to_dict(self) -> dict:
        return {
            "file": self.file,
            "is_dangerous": self.is_dangerous,
            "danger_score": self.danger_score,
            "operations": [
                {
                    "op_type": op.op_type,
                    "target": op.target,
                    "line": op.line,
                    "row_count": op.row_count,
                    "reversible": op.reversible,
                    "message": op.message,
                }
                for op in self.operations
            ],
        }


class MigrationGuard:
    """
    Migration dosyalarını analiz eder.
    Geri dönüşsüz işlemleri, veri kaybı risklerini tespit eder.
    """

    # (pattern, op_type, geri_donuslu_mu, mesaj, öneri)
    DANGEROUS_OPS = [
        (
            r'op\.drop_column\(["\'](\w+)["\'],\s*["\'](\w+)["\']',
            "DROP_COLUMN",
            False,
            "Kolon kalıcı olarak siliniyor",
            "Önce kolonu NULL yapıp deprecated işaretle, bir release sonra sil.",
        ),
        (
            r'op\.drop_table\(["\'](\w+)["\']',
            "DROP_TABLE",
            False,
            "Tablo kalıcı olarak siliniyor",
            "Tabloyu önce boşalt ve yedekle, sonra sil.",
        ),
        (
            r'op\.execute\(["\']TRUNCATE\s+(\w+)',
            "TRUNCATE",
            False,
            "Tüm satırlar siliniyor",
            "Gerçekten tüm veriyi silmek istiyor musun?",
        ),
        (
            r"op\.alter_column\(.*nullable=False",
            "ADD_NOT_NULL",
            True,
            "NOT NULL kısıt ekleniyor — mevcut NULL satırlar migration'ı patlatır",
            "Önce default değer ekle, NULL'ları doldur, sonra NOT NULL ekle.",
        ),
        (
            r'migrations\.DeleteModel\(["\'](\w+)["\']',
            "DROP_TABLE",
            False,
            "Django: model (ve tablo) siliniyor",
            "Modeli önce deprecated yap, veriyi yedekle, sonra sil.",
        ),
        (
            r'migrations\.RemoveField\(\s*model_name=["\'](\w+)["\'],\s*name=["\'](\w+)["\']',
            "DROP_COLUMN",
            False,
            "Django: field siliniyor",
            "Önce field'ı uygulamadan kaldır, sonra migration ile sil.",
        ),
        (
            r"ALTER\s+TABLE\s+(\w+)\s+DROP\s+COLUMN\s+(\w+)",
            "DROP_COLUMN",
            False,
            "Ham SQL: kolon siliniyor",
            "Kolon yedeği al: CREATE TABLE x_backup AS SELECT old_col FROM x",
        ),
        (
            r"ALTER\s+TABLE\s+(\w+)\s+(?:MODIFY|ALTER)\s+COLUMN.*NOT\s+NULL",
            "ADD_NOT_NULL",
            True,
            "Ham SQL: NOT NULL kısıt ekleniyor",
            "NULL satır var mı? SELECT COUNT(*) WHERE col IS NULL",
        ),
    ]

    def __init__(self, connection_string: Optional[str] = None):
        self.connection_string = connection_string
        self._engine = None
        if connection_string and HAS_SQLALCHEMY:
            try:
                self._engine = create_engine(connection_string)
            except Exception:
                pass

    def check_file(self, filepath: str) -> MigrationCheckResult:
        with open(filepath) as f:
            source = f.read()
        return self.check_source(source, filename=os.path.basename(filepath))

    def check_source(
        self, source: str, filename: str = "<source>"
    ) -> MigrationCheckResult:
        ops = []
        source.split("\n")

        for pattern, op_type, reversible, message, suggestion in self.DANGEROUS_OPS:
            for m in re.finditer(pattern, source, re.IGNORECASE | re.DOTALL):
                line_num = source[: m.start()].count("\n") + 1
                # Hedef tablo/kolon adını çıkar
                groups = m.groups()
                if len(groups) >= 2:
                    target = f"{groups[0]}.{groups[1]}"
                elif len(groups) == 1:
                    target = groups[0]
                else:
                    target = "?"

                row_count = (
                    self._get_row_count(target.split(".")[0])
                    if not reversible
                    else None
                )

                ops.append(
                    DangerousOp(
                        op_type=op_type,
                        target=target,
                        line=line_num,
                        row_count=row_count,
                        reversible=reversible,
                        message=message,
                        suggestion=suggestion,
                    )
                )

        danger_score = self._calculate_score(ops)
        return MigrationCheckResult(
            file=filename,
            is_dangerous=len(ops) > 0,
            operations=ops,
            danger_score=danger_score,
        )

    def check_directory(self, migrations_dir: str) -> list[MigrationCheckResult]:
        """Tüm migration klasörünü tara."""
        results = []
        for root, _, files in os.walk(migrations_dir):
            for f in sorted(files):
                if f.endswith(".py") or f.endswith(".sql"):
                    filepath = os.path.join(root, f)
                    result = self.check_file(filepath)
                    if result.is_dangerous:
                        results.append(result)
        return results

    def _get_row_count(self, table: str) -> Optional[int]:
        """Gerçek DB'den satır sayısı çek."""
        if not self._engine:
            return None
        try:
            with self._engine.connect() as conn:
                row = conn.execute(text(f"SELECT COUNT(*) FROM {table}")).fetchone()
                return row[0] if row else None
        except Exception:
            return None

    def _calculate_score(self, ops: list[DangerousOp]) -> int:
        score = 0
        weights = {
            "DROP_TABLE": 40,
            "TRUNCATE": 35,
            "DROP_COLUMN": 25,
            "ADD_NOT_NULL": 15,
            "DATA_LOSS": 30,
        }
        for op in ops:
            base = weights.get(op.op_type, 10)
            if not op.reversible:
                base = int(base * 1.5)
            if op.row_count and op.row_count > 10_000:
                base += 10
            score += base
        return min(score, 100)

    def generate_rollback(self, result: MigrationCheckResult) -> str:
        """
        Tehlikeli migration için rollback şablonu üret.
        """
        lines = [
            f"# Otomatik rollback şablonu — {result.file}",
            f"# Üretildi: {datetime.now().strftime('%Y-%m-%d %H:%M')}",
            "# DİKKAT: Bu şablonu çalıştırmadan önce doğrula!\n",
        ]
        for op in result.operations:
            if op.op_type == "DROP_COLUMN":
                table, col = (
                    op.target.split(".") if "." in op.target else (op.target, "?")
                )
                lines.append(f"# Rollback: {op.op_type} {op.target}")
                lines.append(f"# ALTER TABLE {table} ADD COLUMN {col} <TİP>;")
                lines.append(
                    f"# Yedekten veri: INSERT INTO {table}({col}) SELECT {col} FROM {table}_backup;\n"
                )
            elif op.op_type == "DROP_TABLE":
                lines.append(f"# Rollback: {op.op_type} {op.target}")
                lines.append(
                    f"# CREATE TABLE {op.target} AS SELECT * FROM {op.target}_backup;\n"
                )
        return "\n".join(lines)

    def build_context(self) -> str:
        """YZ için migration bağlam bloğu."""
        return """# aicontract/migration — Kurallar (YZ için otomatik enjekte)
migration_kuralları:
  - DROP COLUMN yazmadan önce "önce deprecated" stratejisini uygula
  - NOT NULL eklemeden önce mevcut NULL satırları kontrol et
  - TRUNCATE/DROP TABLE öncesi yedek al
  - Her destructive işlem için down() migration'ı yaz
  - Büyük tablolarda (>100k satır) migration'ı küçük batch'lere böl
"""
