"""
aicontract — Init + Incremental Baseline + Only-New Modu
Sprint 5 kararları: Beatriz, Noa, Rashid, Amara, Ingrid.

    aicontract init .
        → yaml · ignore · learn · pre-commit hook

    aicontract.learn(".", incremental=True)
        → sadece git diff'ten bu yana değişenler

    aicontract.check(code, only_new=True)
        → baseline'dan öncekiler gizle
"""

import os
import sys
import json
import time
import hashlib
import subprocess
from pathlib import Path
from typing import Optional


# ── aicontract init ─────────────────────────────────────────────────

AICONTRACT_YAML_TEMPLATE = """\
# aicontract yapılandırması
# Oluşturuldu: {date}

# Devre dışı bırakılacak kurallar
ignore_rules: []
#  - AI011   # docstring opsiyonel
#  - PERF004 # iç içe döngüye tolerans

# Atlanacak dizinler (.aicontractignore ile de yapılabilir)
ignore_dirs:
  - __pycache__
  - .git
  - .venv
  - venv
  - env
  - node_modules
  - dist
  - build
  - migrations

# Hata varsa CI'ı durdur
exit_on_error: true
exit_on_warning: false

# Baseline bu kadar günden eskiyse uyar
baseline_max_age_days: 7

# "info" | "warning" | "error" — bunun altını gösterme
min_severity: info

# Veritabanı (opsiyonel)
# database:
#   connection_string: ${{DATABASE_URL}}
#   schema_file: .aicontract_schema.json
"""

AICONTRACTIGNORE_TEMPLATE = """\
# aicontract ignore dosyası — .gitignore syntax'ı
# Bu pattern'lere uyan dosya/dizinler atlanır.

__pycache__/
*.pyc
.venv/
venv/
env/
.env
*.egg-info/
dist/
build/
.mypy_cache/
.pytest_cache/
migrations/
*_migrations/
fixtures/
conftest.py
setup.py
"""

PRE_COMMIT_HOOK = """\
#!/bin/sh
# aicontract pre-commit hook
# Sadece staged değişiklikleri kontrol et (Noa'nın özelliği)

python -c "
import sys, subprocess
sys.path.insert(0, '.')
try:
    import aicontract
    result = aicontract.check_diff(staged_only=True, silent=False)
    sys.exit(result['exit_code'])
except ImportError:
    print('[aicontract] Kurulu değil, atlandı.')
    sys.exit(0)
"
"""

CURSOR_MCP_CONFIG = """\
{{
  "mcpServers": {{
    "aicontract": {{
      "command": "python",
      "args": ["-m", "aicontract.mcp_server"],
      "cwd": "{project_dir}"
    }}
  }}
}}
"""

CLAUDE_DESKTOP_CONFIG = """\
# Claude Desktop için aicontract MCP yapılandırması
# ~/.config/claude/claude_desktop_config.json dosyasına ekle:

{{
  "mcpServers": {{
    "aicontract": {{
      "command": "python",
      "args": ["-m", "aicontract.mcp_server"],
      "cwd": "{project_dir}"
    }}
  }}
}}
"""


def init(project_dir: str = ".", *, force: bool = False, quiet: bool = False) -> dict:
    """
    Projeye aicontract kur. Tek komut, sıfır sürtünme.

    Yapılanlar:
    1. aicontract.yaml oluştur
    2. .aicontractignore oluştur
    3. Proje baseline'ını öğren (learn)
    4. pre-commit hook kur
    5. .cursor/mcp.json oluştur (Cursor varsa)
    6. Claude Desktop config şablonu yaz

    Kullanım:
        import aicontract
        aicontract.init(".")

        # veya CLI:
        # python -m aicontract init .
    """
    results = {}
    project_dir = os.path.abspath(project_dir)
    from datetime import date

    def _log(msg: str, ok: bool = True):
        if not quiet:
            icon = "\033[92m✓\033[0m" if ok else "\033[93m~\033[0m"
            print(f"  {icon}  {msg}")

    print(f"\n\033[1maicontract init\033[0m → {project_dir}\n")

    # 1. aicontract.yaml
    yaml_path = os.path.join(project_dir, "aicontract.yaml")
    if not os.path.exists(yaml_path) or force:
        with open(yaml_path, "w") as f:
            f.write(AICONTRACT_YAML_TEMPLATE.format(date=date.today()))
        _log("aicontract.yaml oluşturuldu")
        results["yaml"] = True
    else:
        _log("aicontract.yaml zaten var, atlandı", ok=False)
        results["yaml"] = False

    # 2. .aicontractignore
    ignore_path = os.path.join(project_dir, ".aicontractignore")
    if not os.path.exists(ignore_path) or force:
        with open(ignore_path, "w") as f:
            f.write(AICONTRACTIGNORE_TEMPLATE)
        _log(".aicontractignore oluşturuldu")
        results["ignore"] = True
    else:
        _log(".aicontractignore zaten var, atlandı", ok=False)
        results["ignore"] = False

    # 3. Baseline — learn()
    _log("Proje taranıyor (learn)...", ok=True)
    try:
        from .smart import learn
        baseline = learn(project_dir, save=True)
        _log(f"Baseline oluşturuldu — {baseline.function_count} fonksiyon")
        results["baseline"] = True
    except Exception as e:
        _log(f"Baseline hatası: {e}", ok=False)
        results["baseline"] = False

    # 4. pre-commit hook
    git_hooks = os.path.join(project_dir, ".git", "hooks")
    if os.path.exists(git_hooks):
        hook_path = os.path.join(git_hooks, "pre-commit")
        if not os.path.exists(hook_path) or force:
            with open(hook_path, "w") as f:
                f.write(PRE_COMMIT_HOOK)
            os.chmod(hook_path, 0o755)
            _log("pre-commit hook kuruldu (.git/hooks/pre-commit)")
            results["pre_commit"] = True
        else:
            _log("pre-commit hook zaten var, atlandı", ok=False)
            results["pre_commit"] = False
    else:
        _log("Git repo bulunamadı — pre-commit hook atlandı", ok=False)
        results["pre_commit"] = False

    # 5. Cursor MCP config
    cursor_dir = os.path.join(project_dir, ".cursor")
    if os.path.exists(cursor_dir):
        mcp_path = os.path.join(cursor_dir, "mcp.json")
        if not os.path.exists(mcp_path) or force:
            with open(mcp_path, "w") as f:
                f.write(CURSOR_MCP_CONFIG.format(project_dir=project_dir))
            _log(".cursor/mcp.json oluşturuldu (Cursor entegrasyonu hazır)")
            results["cursor"] = True
        else:
            _log(".cursor/mcp.json zaten var", ok=False)
    else:
        _log("Cursor bulunamadı — MCP config atlandı", ok=False)
        results["cursor"] = False

    # 6. Claude Desktop şablonu
    claude_config_path = os.path.join(project_dir, ".aicontract_claude_config.json")
    with open(claude_config_path, "w") as f:
        f.write(CLAUDE_DESKTOP_CONFIG.format(project_dir=project_dir))
    _log("Claude Desktop MCP şablonu: .aicontract_claude_config.json")
    results["claude_desktop"] = True

    print(f"\n\033[1m✓ aicontract kurulumu tamamlandı\033[0m")
    print(f"\033[2mMCP başlatmak için: python -m aicontract.mcp_server\033[0m\n")

    return results


# ── Incremental Baseline ─────────────────────────────────────────────

def learn_incremental(project_dir: str = ".", *, baseline_path: str = None) -> bool:
    """
    Rashid + Amara kararı: git diff bazlı güncelleme.
    Son learn()'den bu yana değişen dosyaları bul, sadece onları güncelle.

    Döner: True = güncelleme yapıldı, False = değişiklik yok
    """
    bp = baseline_path or os.path.join(project_dir, ".aicontract_baseline.json")

    if not os.path.exists(bp):
        # Baseline yok — tam learn yap
        from .smart import learn
        learn(project_dir, save=True)
        return True

    # Baseline'ın mtime'ından bu yana değişen dosyalar
    baseline_mtime = os.path.getmtime(bp)
    changed = _files_changed_since(project_dir, baseline_mtime)

    if not changed:
        print("\033[2maicontract/learn: Değişiklik yok, baseline güncel.\033[0m")
        return False

    print(f"\033[1maicontract/learn (incremental)\033[0m — {len(changed)} dosya güncelleniyor...")

    # Mevcut baseline yükle
    try:
        with open(bp) as f:
            data = json.load(f)
        from .smart import ProjectBaseline, _extract_metrics, _build_baseline
        baseline = ProjectBaseline(**data)
    except Exception:
        from .smart import learn
        learn(project_dir, save=True)
        return True

    # Sadece değişen dosyaların metriklerini güncelle
    from .smart import _extract_metrics, _build_baseline
    new_metrics = []
    for filepath in changed:
        full = os.path.join(project_dir, filepath)
        if not os.path.exists(full):
            continue
        try:
            with open(full, encoding="utf-8", errors="replace") as f:
                src = f.read()
            metrics = _extract_metrics(src, filepath)
            new_metrics.extend(metrics)
        except Exception:
            pass

    if new_metrics:
        # Mevcut baseline ile birleştir — basit ağırlıklı güncelleme
        new_baseline = _build_baseline(new_metrics)
        n_old = baseline.function_count
        n_new = new_baseline.function_count
        n_total = n_old + n_new

        if n_total > 0:
            for i in range(len(baseline.means)):
                baseline.means[i] = (
                    baseline.means[i] * n_old + new_baseline.means[i] * n_new
                ) / n_total
            baseline.function_count = n_total

        with open(bp, "w") as f:
            json.dump(_baseline_to_dict(baseline), f, indent=2)

        print(f"  ✓ {len(new_metrics)} fonksiyon güncellendi")

    return True


def _files_changed_since(project_dir: str, since_timestamp: float) -> list[str]:
    """
    Git log'dan değişen dosyaları bul. Git yoksa mtime'a bak.
    """
    # Önce git dene
    try:
        result = subprocess.run(
            ["git", "diff", "--name-only", "--diff-filter=ACM",
             f"@{{%s}}" % int(since_timestamp)],
            cwd=project_dir, capture_output=True, text=True, timeout=10
        )
        if result.returncode == 0 and result.stdout.strip():
            return [f for f in result.stdout.splitlines() if f.endswith(".py")]
    except Exception:
        pass

    # Git yok — mtime ile tara
    changed = []
    skip = {"__pycache__", ".git", ".venv", "venv", "env"}
    for root, dirs, files in os.walk(project_dir):
        dirs[:] = [d for d in dirs if d not in skip]
        for fname in files:
            if not fname.endswith(".py"):
                continue
            full = os.path.join(root, fname)
            try:
                if os.path.getmtime(full) > since_timestamp:
                    changed.append(os.path.relpath(full, project_dir))
            except Exception:
                pass
    return changed


def _baseline_to_dict(baseline) -> dict:
    from dataclasses import asdict
    return asdict(baseline)


# ── Only-New Modu — Ingrid + Beatriz kararı ─────────────────────────

class ViolationBaseline:
    """
    Mevcut violation'ları kaydeder.
    Sonraki kontrollerde "yeni" olanları ayırt eder.
    """

    def __init__(self, save_path: str = ".aicontract_violations.json"):
        self.save_path = save_path
        self._known: dict[str, set] = {}  # filepath → {violation_fingerprint}
        self._load()

    def _fingerprint(self, v) -> str:
        return hashlib.md5(
            f"{v.rule_id}|{v.line}|{v.message[:50]}".encode()
        ).hexdigest()[:12]

    def _load(self):
        if not os.path.exists(self.save_path):
            return
        try:
            with open(self.save_path) as f:
                data = json.load(f)
            self._known = {k: set(v) for k, v in data.items()}
        except Exception:
            pass

    def _save(self):
        try:
            with open(self.save_path, "w") as f:
                json.dump({k: list(v) for k, v in self._known.items()}, f)
        except Exception:
            pass

    def filter_new(self, violations: list, filepath: str) -> list:
        """Sadece yeni (baseline'da olmayan) violation'ları döndür."""
        known = self._known.get(filepath, set())
        return [v for v in violations if self._fingerprint(v) not in known]

    def record(self, violations: list, filepath: str):
        """Mevcut violation'ları baseline'a kaydet."""
        fps = {self._fingerprint(v) for v in violations}
        self._known[filepath] = fps
        self._save()

    def snapshot(self, project_dir: str = "."):
        """
        Tüm projeyi tara ve mevcut violation'ları baseline olarak kaydet.
        "Şu an ne varsa kabul et, yenileri yakala" modu.
        """
        from .inline import check
        skip = {"__pycache__", ".git", ".venv", "venv", "env"}
        count = 0
        for root, dirs, files in os.walk(project_dir):
            dirs[:] = [d for d in dirs if d not in skip]
            for fname in files:
                if not fname.endswith(".py"):
                    continue
                full = os.path.join(root, fname)
                rel = os.path.relpath(full, project_dir)
                try:
                    with open(full, encoding="utf-8", errors="replace") as f:
                        src = f.read()
                    violations = check(src, silent=True)
                    self.record(violations, rel)
                    count += 1
                except Exception:
                    pass
        print(f"\033[1maicontract/snapshot\033[0m — {count} dosya baseline'a alındı")


# ── Global violation baseline (singleton) ───────────────────────────
_v_baseline: Optional[ViolationBaseline] = None


def get_violation_baseline(project_dir: str = ".") -> ViolationBaseline:
    global _v_baseline
    if _v_baseline is None:
        path = os.path.join(project_dir, ".aicontract_violations.json")
        _v_baseline = ViolationBaseline(path)
    return _v_baseline
