"""
aicontract — MCP Sunucusu
Toplantı kararı: stdio transport, 4 tool, ağ yok.

Kemal: saldırı yüzeyi sıfır — process stdio, token yok, port yok.
Noa: 4 tool — fazlası gürültü.
Marco: Cursor + Claude Code hazır.

Çalıştırma:
    python -m aicontract.mcp_server

MCP tool listesi:
    analyze_function(name, filepath)   → violations + impact + context
    check_snippet(code, filepath?)     → anında violations
    get_context(function_name)         → YZ için bağlam bloğu
    project_health()                   → genel skor özeti
"""

import sys
import json
import os
import ast
import textwrap
import traceback
from typing import Any


# ── MCP Protokolü (stdlib only, JSON-RPC 2.0 üstüne) ────────────────

def _send(obj: dict):
    """stdout'a JSON-RPC mesajı yaz."""
    line = json.dumps(obj, ensure_ascii=False)
    sys.stdout.write(line + "\n")
    sys.stdout.flush()


def _recv() -> dict | None:
    """stdin'den bir satır oku, parse et."""
    try:
        line = sys.stdin.readline()
        if not line:
            return None
        return json.loads(line.strip())
    except (json.JSONDecodeError, EOFError):
        return None


def _ok(req_id: Any, result: Any):
    _send({"jsonrpc": "2.0", "id": req_id, "result": result})


def _err(req_id: Any, code: int, message: str):
    _send({"jsonrpc": "2.0", "id": req_id,
           "error": {"code": code, "message": message}})


# ── Tool tanımları (MCP initialize'da gönderilir) ────────────────────

TOOLS = [
    {
        "name": "analyze_function",
        "description": (
            "Bir Python fonksiyonunu analiz eder. "
            "Violations, impact analizi ve YZ bağlam bloğu döner. "
            "Fonksiyon değiştirmeden önce çağır."
        ),
        "inputSchema": {
            "type": "object",
            "properties": {
                "function_name": {
                    "type": "string",
                    "description": "Analiz edilecek fonksiyon adı"
                },
                "filepath": {
                    "type": "string",
                    "description": "Fonksiyonun bulunduğu dosya yolu"
                },
                "project_dir": {
                    "type": "string",
                    "description": "Proje kök dizini (opsiyonel, varsayılan: .)",
                    "default": "."
                }
            },
            "required": ["function_name", "filepath"]
        }
    },
    {
        "name": "check_snippet",
        "description": (
            "Bir Python kod parçasını anında analiz eder. "
            "YZ yeni kod yazdığında çağır — commit etmeden önce."
        ),
        "inputSchema": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "string",
                    "description": "Analiz edilecek Python kodu"
                },
                "filepath": {
                    "type": "string",
                    "description": "Kod parçasının hangi dosyaya ait olduğu (opsiyonel)"
                },
                "only_errors": {
                    "type": "boolean",
                    "description": "Sadece error seviyesini döndür",
                    "default": False
                }
            },
            "required": ["code"]
        }
    },
    {
        "name": "get_context",
        "description": (
            "Bir fonksiyon için YZ'ye verilecek bağlam bloğunu üretir. "
            "Mimari kararlar, impact analizi, DB şeması içerir. "
            "Değişiklik yapmadan önce bu bağlamı prompt'a ekle."
        ),
        "inputSchema": {
            "type": "object",
            "properties": {
                "function_name": {
                    "type": "string",
                    "description": "Bağlam üretilecek fonksiyon"
                },
                "task": {
                    "type": "string",
                    "description": "YZ'nin yapacağı iş (opsiyonel)"
                },
                "project_dir": {
                    "type": "string",
                    "description": "Proje kök dizini",
                    "default": "."
                }
            },
            "required": ["function_name"]
        }
    },
    {
        "name": "project_health",
        "description": (
            "Projenin genel sağlık durumunu özetler. "
            "Toplam skor, en riskli dosyalar, son değişiklikler. "
            "Oturum başında bir kez çağır."
        ),
        "inputSchema": {
            "type": "object",
            "properties": {
                "project_dir": {
                    "type": "string",
                    "description": "Proje kök dizini",
                    "default": "."
                },
                "top": {
                    "type": "integer",
                    "description": "Kaç hotspot gösterilsin",
                    "default": 5
                }
            }
        }
    }
]


# ── Tool implementasyonları ──────────────────────────────────────────

def _tool_check_snippet(code: str, filepath: str = None, only_errors: bool = False) -> dict:
    """check_snippet tool implementasyonu."""
    try:
        from .inline import check
        from .config import filter_violations, get_config

        violations = check(code, silent=True, filepath=filepath)

        if only_errors:
            violations = [v for v in violations if v.severity == "error"]

        v_list = [
            {
                "rule_id": v.rule_id,
                "severity": v.severity,
                "message": v.message,
                "line": v.line,
                "suggestion": v.suggestion,
                "fix": v.fix,
            }
            for v in violations
        ]

        errors   = sum(1 for v in violations if v.severity == "error")
        warnings = sum(1 for v in violations if v.severity == "warning")

        # YZ'nin anlayacağı özet
        if not violations:
            summary = "Temiz — ihlal bulunamadı."
        else:
            summary = (
                f"{len(violations)} ihlal: {errors} hata, {warnings} uyarı. "
                f"Commit etmeden önce düzelt."
            )

        return {
            "summary": summary,
            "violations": v_list,
            "score": _quick_score(violations),
            "safe_to_commit": errors == 0,
        }
    except Exception as e:
        return {"error": str(e), "violations": [], "safe_to_commit": False}


def _tool_analyze_function(
    function_name: str,
    filepath: str,
    project_dir: str = "."
) -> dict:
    """analyze_function tool implementasyonu."""
    try:
        full_path = os.path.join(project_dir, filepath)

        # Dosyayı oku
        try:
            from .config import safe_read_file
            source, err = safe_read_file(full_path)
            if err:
                return {"error": err}
        except ImportError:
            with open(full_path, encoding="utf-8", errors="replace") as f:
                source = f.read()

        if not source:
            return {"error": "Dosya boş veya okunamadı"}

        # Fonksiyonu çıkar
        fn_source = _extract_function_source(source, function_name)
        if not fn_source:
            return {"error": f"'{function_name}' fonksiyonu bulunamadı: {filepath}"}

        # Violations
        from .inline import check
        violations = check(fn_source, silent=True, filepath=filepath)
        v_list = [
            {
                "rule_id": v.rule_id,
                "severity": v.severity,
                "message": v.message,
                "line": v.line,
                "suggestion": v.suggestion,
            }
            for v in violations
        ]

        # Intent
        try:
            from .smart import extract_intent
            intent = extract_intent(fn_source)
            intent_info = {
                "category": intent.category,
                "confidence": intent.confidence,
                "expectations": intent.expectations,
            }
        except Exception:
            intent_info = {}

        # Impact analizi
        try:
            from .smart import _build_static_call_graph, _bfs_impact
            graph = _build_static_call_graph(project_dir)
            impact = _bfs_impact(function_name, graph)
            impact_info = {
                "direct": list(impact.get("direct", set())),
                "indirect": list(impact.get("indirect", set())),
                "risk_score": impact.get("score", 0),
            }
        except Exception:
            impact_info = {"direct": [], "indirect": [], "risk_score": 0}

        errors = sum(1 for v in violations if v.severity == "error")

        return {
            "function": function_name,
            "filepath": filepath,
            "violations": v_list,
            "violation_count": len(violations),
            "safe_to_modify": errors == 0,
            "intent": intent_info,
            "impact": impact_info,
            "warning": (
                f"Bu fonksiyonu değiştirirsen {len(impact_info['direct'])} "
                f"doğrudan bağımlı etkilenir."
                if impact_info["direct"] else None
            ),
        }
    except Exception as e:
        return {"error": str(e)}


def _tool_get_context(
    function_name: str,
    task: str = None,
    project_dir: str = "."
) -> dict:
    """get_context tool implementasyonu."""
    try:
        from .context_gen import ContextGenerator
        from .session_memory import SessionMemory

        mem_path = os.path.join(project_dir, ".aicontract_memory.yaml")
        mem = SessionMemory(mem_path) if os.path.exists(mem_path) else None

        gen = ContextGenerator(project_dir=project_dir, memory=mem)
        context_block = gen.wrap(
            user_prompt=task or f"{function_name} fonksiyonunu değiştir",
            target_fn=function_name,
        )

        return {
            "context_block": context_block,
            "instruction": (
                "Bu bağlam bloğunu görevin başına ekle. "
                "İçindeki kurallara uymak ZORUNLU."
            ),
        }
    except Exception as e:
        # Context gen yoksa basit bağlam üret
        return {
            "context_block": (
                f"# aicontract bağlam — {function_name}\n"
                f"# Hedef fonksiyon: {function_name}\n"
                f"# Görev: {task or 'belirtilmedi'}\n"
            ),
            "instruction": "Bağlam üreticisi bulunamadı, basit bağlam döndürüldü.",
        }


def _tool_project_health(project_dir: str = ".", top: int = 5) -> dict:
    """project_health tool implementasyonu."""
    try:
        result = {
            "project_dir": project_dir,
            "hotspots": [],
            "baseline_age_warning": None,
            "summary": "",
        }

        # Hotspot analizi
        try:
            from .hotspot import hotspot
            report = hotspot(project_dir, top=top, silent=True)
            result["has_git"] = report.has_git
            result["total_commits"] = report.total_commits
            result["hotspots"] = [
                {
                    "filepath": h.filepath,
                    "risk_level": h.risk_level,
                    "risk_score": h.risk_score,
                    "change_count": h.change_count,
                    "bug_fix_count": h.bug_fix_count,
                }
                for h in report.top_files(top)
            ]
        except Exception:
            result["has_git"] = False

        # Baseline yaşı
        try:
            from .config import check_baseline_age
            warn = check_baseline_age(os.path.join(project_dir, ".aicontract_baseline.json"))
            result["baseline_age_warning"] = warn
        except Exception:
            pass

        # Genel skor (rastgele 5 dosya örnekle)
        total_v = 0
        files_checked = 0
        skip = {"__pycache__", ".git", ".venv", "venv", "env", "node_modules"}
        for root, dirs, files in os.walk(project_dir):
            dirs[:] = [d for d in dirs if d not in skip]
            for fname in files[:3]:  # her dizinden 3 dosya
                if not fname.endswith(".py"):
                    continue
                try:
                    from .inline import check
                    with open(os.path.join(root, fname),
                              encoding="utf-8", errors="replace") as f:
                        src = f.read()
                    v = check(src, silent=True)
                    total_v += len(v)
                    files_checked += 1
                    if files_checked >= 20:  # max 20 dosya örnekle
                        break
                except Exception:
                    pass
            if files_checked >= 20:
                break

        avg = total_v / max(files_checked, 1)
        health = "İyi" if avg < 2 else "Orta" if avg < 5 else "Zayıf"
        result["summary"] = (
            f"{files_checked} dosya örneklendi, "
            f"dosya başına ortalama {avg:.1f} ihlal — {health}"
        )
        result["sampled_files"] = files_checked
        result["avg_violations_per_file"] = round(avg, 1)
        result["health"] = health

        return result
    except Exception as e:
        return {"error": str(e), "summary": "Analiz başarısız"}


# ── Yardımcılar ──────────────────────────────────────────────────────

def _extract_function_source(source: str, fn_name: str) -> str | None:
    try:
        tree = ast.parse(source)
        lines = source.split("\n")
        for node in ast.walk(tree):
            if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
                if node.name == fn_name:
                    return "\n".join(lines[node.lineno - 1:node.end_lineno])
    except Exception:
        pass
    return None


def _quick_score(violations: list) -> int:
    w = {"error": 20, "warning": 8, "info": 2}
    return min(sum(w.get(v.severity, 0) for v in violations), 100)


# ── Ana MCP döngüsü ──────────────────────────────────────────────────

def _handle_request(req: dict) -> None:
    method = req.get("method", "")
    req_id = req.get("id")
    params = req.get("params", {})

    # ── initialize ────────────────────────────────────────────────────
    if method == "initialize":
        _ok(req_id, {
            "protocolVersion": "2024-11-05",
            "capabilities": {"tools": {}},
            "serverInfo": {
                "name": "aicontract",
                "version": "2.1.0",
                "description": "YZ kod kalite sistemi — LLM'siz, deterministik",
            },
        })
        return

    # ── notifications (cevap yok) ─────────────────────────────────────
    if method.startswith("notifications/"):
        return

    # ── tools/list ───────────────────────────────────────────────────
    if method == "tools/list":
        _ok(req_id, {"tools": TOOLS})
        return

    # ── tools/call ───────────────────────────────────────────────────
    if method == "tools/call":
        tool_name = params.get("name", "")
        args = params.get("arguments", {})

        try:
            if tool_name == "check_snippet":
                result = _tool_check_snippet(
                    code=args["code"],
                    filepath=args.get("filepath"),
                    only_errors=args.get("only_errors", False),
                )
            elif tool_name == "analyze_function":
                result = _tool_analyze_function(
                    function_name=args["function_name"],
                    filepath=args["filepath"],
                    project_dir=args.get("project_dir", "."),
                )
            elif tool_name == "get_context":
                result = _tool_get_context(
                    function_name=args["function_name"],
                    task=args.get("task"),
                    project_dir=args.get("project_dir", "."),
                )
            elif tool_name == "project_health":
                result = _tool_project_health(
                    project_dir=args.get("project_dir", "."),
                    top=args.get("top", 5),
                )
            else:
                _err(req_id, -32601, f"Bilinmeyen tool: {tool_name}")
                return

            _ok(req_id, {
                "content": [{"type": "text", "text": json.dumps(result, ensure_ascii=False, indent=2)}]
            })

        except KeyError as e:
            _err(req_id, -32602, f"Eksik parametre: {e}")
        except Exception as e:
            _err(req_id, -32603, f"Tool hatası: {e}\n{traceback.format_exc()}")
        return

    # ── Bilinmeyen method ─────────────────────────────────────────────
    if req_id is not None:
        _err(req_id, -32601, f"Method bulunamadı: {method}")


def run():
    """MCP sunucusunu başlat — stdio döngüsü."""
    # stderr'e log (stdout MCP için ayrılmış)
    print("[aicontract MCP] Başlatıldı. stdin dinleniyor...", file=sys.stderr)

    while True:
        req = _recv()
        if req is None:
            break  # stdin kapandı — çık
        try:
            _handle_request(req)
        except Exception as e:
            print(f"[aicontract MCP] Beklenmeyen hata: {e}", file=sys.stderr)

    print("[aicontract MCP] Kapatıldı.", file=sys.stderr)


if __name__ == "__main__":
    run()
