"""
aicontract — Session Memory
Mimari kararları kalıcı tutar. YZ oturumdan oturuma unutmaz.

Problem: YZ her prompt'ta sıfırdan başlar. "Hata yönetimi için
custom exception kullan" kararını 5 prompt sonra unutur.

Çözüm: Kararları YAML'a yaz → her prompt'a otomatik enjekte et.

Kullanım:
    from aicontract.session_memory import SessionMemory

    mem = SessionMemory(".aicontract_memory.yaml")
    mem.add_decision(
        category="error_handling",
        rule="Custom exception kullan, asla bare except",
        reason="Stack trace korunuyor",
    )
    print(mem.build_context())  # prompt prefix'e ekle
"""

import os
import yaml
import hashlib
from datetime import datetime
from dataclasses import dataclass, field, asdict
from typing import Optional


@dataclass
class ArchDecision:
    category: str        # error_handling | naming | db | api | testing | security
    rule: str            # "Custom exception kullan"
    reason: str          # "Stack trace korunuyor"
    added: str = field(default_factory=lambda: datetime.now().strftime("%Y-%m-%d"))
    enforced: bool = True   # False = tavsiye, True = zorunlu
    uid: str = ""

    def __post_init__(self):
        if not self.uid:
            self.uid = hashlib.md5(
                f"{self.category}{self.rule}".encode()
            ).hexdigest()[:8]


CATEGORY_LABELS = {
    "error_handling": "Hata yönetimi",
    "naming": "İsimlendirme",
    "db": "Veritabanı",
    "api": "API tasarımı",
    "testing": "Test stratejisi",
    "security": "Güvenlik",
    "architecture": "Mimari",
    "performance": "Performans",
}

# Akıllı çıkarım: kod kaynağından karar tespit et
INFERENCE_PATTERNS = [
    # (regex, category, rule)
    (r"class\s+\w+Exception\s*\(", "error_handling",
     "Custom exception sınıfları tanımlanmış — bare except kullanma"),
    (r"@pytest\.mark\.parametrize", "testing",
     "Parametrik testler kullanılıyor — her yeni fonksiyon için parametrik test yaz"),
    (r"from\s+pydantic", "api",
     "Pydantic kullanılıyor — tüm API giriş/çıkışlarına model ekle"),
    (r"logging\.\w+\(", "error_handling",
     "Python logging kullanılıyor — print() ile log yazma"),
    (r"async\s+def", "architecture",
     "Async kodbase — senkron bloklar ekleme"),
    (r"from\s+sqlalchemy", "db",
     "SQLAlchemy ORM kullanılıyor — ham SQL sorgusu yazma"),
    (r"class\s+\w+Repository", "db",
     "Repository pattern uygulanmış — doğrudan DB çağrısı yapma"),
    (r"@app\.route|@router\.", "api",
     "REST endpoint'leri var — şema doğrulamasız endpoint ekleme"),
    (r'os\.environ\[|os\.getenv\(', "security",
     "Env var kullanılıyor — secret'ı asla koda gömme"),
]


class SessionMemory:
    """
    Proje mimari kararlarını saklar ve YZ'ye enjekte eder.
    """

    def __init__(self, memory_file: str = ".aicontract_memory.yaml"):
        self.memory_file = memory_file
        self.decisions: list[ArchDecision] = []
        self._load()

    # ── Kaydet / Yükle ──────────────────────────────────────────────

    def _load(self):
        if not os.path.exists(self.memory_file):
            return
        with open(self.memory_file) as f:
            data = yaml.safe_load(f) or {}
        for d in data.get("decisions", []):
            self.decisions.append(ArchDecision(**d))

    def _save(self):
        data = {"decisions": [asdict(d) for d in self.decisions]}
        with open(self.memory_file, "w") as f:
            yaml.dump(data, f, allow_unicode=True, default_flow_style=False)

    # ── Karar ekle / çıkar ──────────────────────────────────────────

    def add_decision(
        self,
        category: str,
        rule: str,
        reason: str = "",
        enforced: bool = True,
    ) -> ArchDecision:
        d = ArchDecision(category=category, rule=rule, reason=reason, enforced=enforced)
        # Aynı karar iki kez girmesin
        existing_uids = {dec.uid for dec in self.decisions}
        if d.uid not in existing_uids:
            self.decisions.append(d)
            self._save()
            print(f"[aicontract/memory] Karar eklendi: [{category}] {rule}")
        return d

    def remove_decision(self, uid: str):
        self.decisions = [d for d in self.decisions if d.uid != uid]
        self._save()

    def list_decisions(self, category: Optional[str] = None) -> list[ArchDecision]:
        if category:
            return [d for d in self.decisions if d.category == category]
        return self.decisions

    # ── Akıllı çıkarım ──────────────────────────────────────────────

    def infer_from_source(self, source: str, auto_add: bool = True) -> list[ArchDecision]:
        """
        Kaynak koddan mimari kararları otomatik çıkar.
        Yeni bulunanları memory'ye ekle.
        """
        import re
        found = []
        for pattern, category, rule in INFERENCE_PATTERNS:
            if re.search(pattern, source):
                d = ArchDecision(category=category, rule=rule, reason="Koddan çıkarıldı", enforced=False)
                found.append(d)
                if auto_add:
                    existing = {dec.uid for dec in self.decisions}
                    if d.uid not in existing:
                        self.decisions.append(d)
        if auto_add and found:
            self._save()
        return found

    def scan_project(self, project_dir: str) -> list[ArchDecision]:
        """Tüm .py dosyalarını tarayıp kararları çıkar."""
        all_found = []
        for root, _, files in os.walk(project_dir):
            for f in files:
                if f.endswith(".py") and not f.startswith("test_"):
                    try:
                        with open(os.path.join(root, f)) as fh:
                            source = fh.read()
                        found = self.infer_from_source(source, auto_add=True)
                        all_found.extend(found)
                    except Exception:
                        pass
        return all_found

    # ── Çelişki tespiti ─────────────────────────────────────────────

    def check_violations(self, source: str) -> list[dict]:
        """
        Yeni yazılan kodu mevcut kararlara karşı kontrol et.
        """
        import re
        violations = []
        violation_patterns = {
            "error_handling": [
                (r"\bexcept\s*:", "bare except kullanıldı"),
                (r"\bprint\s*\(.*error\b", "hata print ile loglanıyor"),
            ],
            "security": [
                (r'password\s*=\s*["\'][^"\']+["\']', "hardcoded password"),
                (r'secret\s*=\s*["\'][^"\']+["\']', "hardcoded secret"),
            ],
            "db": [
                (r'f["\'].*SELECT', "f-string SQL sorgusu"),
            ],
        }

        for decision in self.decisions:
            if not decision.enforced:
                continue
            patterns = violation_patterns.get(decision.category, [])
            for pattern, desc in patterns:
                for m in re.finditer(pattern, source, re.IGNORECASE):
                    line = source[:m.start()].count("\n") + 1
                    violations.append({
                        "decision_uid": decision.uid,
                        "rule": decision.rule,
                        "violation": desc,
                        "line": line,
                    })
        return violations

    # ── Context Generator ────────────────────────────────────────────

    def build_context(self, include_reason: bool = False) -> str:
        """
        YZ'ye verilecek mimari karar bloğu.
        Her prompt'un başına eklenir.
        """
        if not self.decisions:
            return "# aicontract/memory: henüz kayıtlı mimari karar yok\n"

        lines = ["# aicontract/memory — Mimari kararlar (YZ için otomatik enjekte)"]
        lines.append("# Bu kurallara UYMAK ZORUNDASIN:\n")

        # Kategoriye göre grupla
        by_cat: dict[str, list[ArchDecision]] = {}
        for d in self.decisions:
            by_cat.setdefault(d.category, []).append(d)

        for cat, decisions in by_cat.items():
            label = CATEGORY_LABELS.get(cat, cat)
            lines.append(f"{label}:")
            for d in decisions:
                prefix = "  ZORUNLU:" if d.enforced else "  Tavsiye:"
                lines.append(f"  {prefix} {d.rule}")
                if include_reason and d.reason:
                    lines.append(f"    # Neden: {d.reason}")
            lines.append("")

        return "\n".join(lines)

    def summary(self) -> str:
        total = len(self.decisions)
        enforced = sum(1 for d in self.decisions if d.enforced)
        cats = len({d.category for d in self.decisions})
        return (
            f"[aicontract/memory] {total} karar "
            f"({enforced} zorunlu, {total-enforced} tavsiye) "
            f"— {cats} kategori"
        )
