#!/usr/bin/env python3
"""
Eden SAGE - Model Context Protocol (MCP) Server v2.0
Production-grade MCP server exposing Eden's full security capabilities.

Allows Claude, Cursor, Windsurf, GPT, and any MCP-compatible AI to:
- Audit repositories for vulnerabilities
- Scan code snippets for secrets
- Check dependencies for CVEs
- Get security recommendations

This is Eden's gateway to the global AI ecosystem.
"""
import sys
import os
import json
import asyncio
import sqlite3
import subprocess
import tempfile
import shutil
import re
from datetime import datetime
from pathlib import Path
from typing import Optional

# Add Eden to path
sys.path.insert(0, '/Eden/CORE')

# MCP imports
try:
    from mcp.server.fastmcp import FastMCP
except ImportError:
    print("Installing MCP SDK...")
    subprocess.run([sys.executable, "-m", "pip", "install", "mcp[cli]", "fastmcp", "--break-system-packages"], 
                   capture_output=True)
    from mcp.server.fastmcp import FastMCP

# Configuration
DB_PATH = "/Eden/DATA/mcp_requests.db"
LOG_PATH = "/Eden/LOGS/mcp_server.log"
TEMP_DIR = "/Eden/TEMP/mcp_audits"
CLONE_TIMEOUT = 120
MAX_REPO_SIZE_MB = 200

# Secret patterns for scanning
SECRET_PATTERNS = [
    (r'(?i)(api[_-]?key|apikey)\s*[=:]\s*["\']?([a-zA-Z0-9_\-]{20,})["\']?', 'API Key Exposure'),
    (r'(?i)(secret|password|passwd|pwd)\s*[=:]\s*["\']?([^\s"\']{8,})["\']?', 'Hardcoded Secret'),
    (r'(?i)(aws_access_key_id|aws_secret_access_key)\s*[=:]\s*["\']?([A-Z0-9]{16,})["\']?', 'AWS Credentials'),
    (r'ghp_[a-zA-Z0-9]{36}', 'GitHub Personal Access Token'),
    (r'sk-[a-zA-Z0-9]{48}', 'OpenAI API Key'),
    (r'-----BEGIN (RSA |DSA |EC |OPENSSH )?PRIVATE KEY-----', 'Private Key Exposed'),
    (r'(?i)bearer\s+[a-zA-Z0-9_\-\.]{20,}', 'Bearer Token'),
    (r'(?i)(mongodb|postgres|mysql|redis)://[^\s]+', 'Database Connection String'),
    (r'xox[baprs]-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24}', 'Slack Token'),
    (r'(?i)stripe[_-]?(test|live)?[_-]?(secret|publishable)?[_-]?key\s*[=:]\s*["\']?(sk_|pk_)[a-zA-Z0-9]{24,}["\']?', 'Stripe Key'),
]

# Code quality patterns
QUALITY_PATTERNS = [
    (r'\beval\s*\(', 'Dangerous eval() usage'),
    (r'\bexec\s*\(', 'Dangerous exec() usage'),
    (r'subprocess\.(call|run|Popen).*shell\s*=\s*True', 'Shell injection risk'),
    (r'os\.system\s*\(', 'OS command injection risk'),
    (r'pickle\.(load|loads)\s*\(', 'Insecure deserialization'),
    (r'yaml\.load\s*\([^)]*\)', 'Unsafe YAML loading'),
    (r'\.format\s*\([^)]*\)\s*$', 'Potential format string vulnerability'),
    (r'(?i)TODO|FIXME|HACK|XXX', 'Technical debt marker'),
    (r'except\s*:', 'Bare except clause'),
    (r'assert\s+', 'Assert in production code'),
]

def log(msg: str):
    """Log to file"""
    ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    line = f"[{ts}] {msg}"
    print(line, file=sys.stderr)
    try:
        with open(LOG_PATH, 'a') as f:
            f.write(line + "\n")
    except:
        pass

def init_db():
    """Initialize tracking database"""
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute("""CREATE TABLE IF NOT EXISTS mcp_requests (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        timestamp TEXT,
        tool_name TEXT,
        input_summary TEXT,
        result_summary TEXT,
        client_info TEXT,
        duration_ms INTEGER
    )""")
    c.execute("""CREATE TABLE IF NOT EXISTS mcp_leads (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        timestamp TEXT,
        repo_url TEXT,
        findings_count INTEGER,
        converted BOOLEAN DEFAULT 0
    )""")
    conn.commit()
    conn.close()

def log_request(tool_name: str, input_summary: str, result_summary: str, duration_ms: int):
    """Log MCP request for analytics"""
    try:
        conn = sqlite3.connect(DB_PATH)
        c = conn.cursor()
        c.execute("""INSERT INTO mcp_requests 
                    (timestamp, tool_name, input_summary, result_summary, duration_ms)
                    VALUES (?, ?, ?, ?, ?)""",
                 (datetime.now().isoformat(), tool_name, input_summary[:200], result_summary[:200], duration_ms))
        conn.commit()
        conn.close()
    except Exception as e:
        log(f"DB log error: {e}")

def log_lead(repo_url: str, findings_count: int):
    """Log potential lead from MCP audit"""
    try:
        conn = sqlite3.connect(DB_PATH)
        c = conn.cursor()
        c.execute("""INSERT INTO mcp_leads (timestamp, repo_url, findings_count)
                    VALUES (?, ?, ?)""",
                 (datetime.now().isoformat(), repo_url, findings_count))
        conn.commit()
        conn.close()
    except:
        pass

# Initialize
init_db()
Path(TEMP_DIR).mkdir(parents=True, exist_ok=True)

# Create MCP Server
mcp = FastMCP(
    "Eden SAGE Security"
)

def clone_repo(url: str) -> Optional[str]:
    """Clone a repository with timeout and size limits"""
    repo_name = url.rstrip('/').split('/')[-1].replace('.git', '')
    target_dir = os.path.join(TEMP_DIR, f"{repo_name}_{datetime.now().strftime('%H%M%S')}")
    
    try:
        result = subprocess.run(
            ['git', 'clone', '--depth', '1', '--single-branch', url, target_dir],
            timeout=CLONE_TIMEOUT,
            capture_output=True
        )
        if result.returncode == 0 and os.path.exists(target_dir):
            return target_dir
    except subprocess.TimeoutExpired:
        log(f"Clone timeout: {url}")
    except Exception as e:
        log(f"Clone error: {e}")
    
    return None

def scan_file_for_secrets(content: str, filename: str = "unknown") -> list:
    """Scan file content for secrets"""
    findings = []
    lines = content.split('\n')
    
    for i, line in enumerate(lines, 1):
        for pattern, issue_type in SECRET_PATTERNS:
            if re.search(pattern, line):
                # Don't include the actual secret in output
                masked_line = re.sub(r'[a-zA-Z0-9]{10,}', '[REDACTED]', line)
                findings.append({
                    "type": "secret",
                    "severity": "CRITICAL",
                    "issue": issue_type,
                    "file": filename,
                    "line": i,
                    "preview": masked_line.strip()[:100]
                })
    
    return findings

def scan_file_for_quality(content: str, filename: str = "unknown") -> list:
    """Scan file for code quality issues"""
    findings = []
    lines = content.split('\n')
    
    for i, line in enumerate(lines, 1):
        for pattern, issue_type in QUALITY_PATTERNS:
            if re.search(pattern, line):
                findings.append({
                    "type": "quality",
                    "severity": "MEDIUM" if "TODO" in issue_type else "HIGH",
                    "issue": issue_type,
                    "file": filename,
                    "line": i,
                    "preview": line.strip()[:100]
                })
    
    return findings

def scan_directory(target_dir: str) -> list:
    """Scan entire directory for issues"""
    findings = []
    
    # File extensions to scan
    extensions = {'.py', '.js', '.ts', '.jsx', '.tsx', '.java', '.go', '.rb', 
                  '.php', '.cs', '.cpp', '.c', '.h', '.sh', '.bash', '.yml', 
                  '.yaml', '.json', '.env', '.config', '.conf', '.ini'}
    
    for root, dirs, files in os.walk(target_dir):
        # Skip hidden and vendor directories
        dirs[:] = [d for d in dirs if not d.startswith('.') and d not in 
                   ['node_modules', 'vendor', 'venv', '.git', '__pycache__', 'dist', 'build']]
        
        for filename in files:
            if any(filename.endswith(ext) for ext in extensions) or filename.startswith('.env'):
                filepath = os.path.join(root, filename)
                rel_path = os.path.relpath(filepath, target_dir)
                
                try:
                    with open(filepath, 'r', errors='ignore') as f:
                        content = f.read(100000)  # Limit to 100KB per file
                    
                    findings.extend(scan_file_for_secrets(content, rel_path))
                    findings.extend(scan_file_for_quality(content, rel_path))
                except:
                    pass
    
    return findings

# ============== MCP TOOLS ==============

@mcp.tool()
async def audit_repository(repo_url: str) -> str:
    """
    Perform a comprehensive security audit on a GitHub repository.
    
    Scans for:
    - Hardcoded secrets (API keys, passwords, tokens)
    - Security vulnerabilities (eval, exec, shell injection)
    - Code quality issues (technical debt, bare excepts)
    
    Args:
        repo_url: GitHub repository URL (e.g., https://github.com/owner/repo)
    
    Returns:
        JSON report with findings, severity levels, and file locations.
    """
    start = datetime.now()
    log(f"MCP audit_repository: {repo_url}")
    
    # Validate URL
    if not repo_url or 'github.com' not in repo_url:
        return json.dumps({"error": "Invalid GitHub URL. Please provide a valid repository URL."})
    
    # Clone
    target_dir = clone_repo(repo_url)
    if not target_dir:
        return json.dumps({"error": f"Failed to clone repository. It may be private or not exist."})
    
    try:
        # Scan
        findings = scan_directory(target_dir)
        
        # Categorize
        critical = [f for f in findings if f.get('severity') == 'CRITICAL']
        high = [f for f in findings if f.get('severity') == 'HIGH']
        medium = [f for f in findings if f.get('severity') == 'MEDIUM']
        
        # Log as potential lead
        if findings:
            log_lead(repo_url, len(findings))
        
        result = {
            "repository": repo_url,
            "scan_time": datetime.now().isoformat(),
            "summary": {
                "total_issues": len(findings),
                "critical": len(critical),
                "high": len(high),
                "medium": len(medium)
            },
            "top_findings": findings[:10],  # Limit context
            "recommendation": "Run `/get_fix_quote` for remediation pricing" if findings else "Repository appears secure"
        }
        
        duration = int((datetime.now() - start).total_seconds() * 1000)
        log_request("audit_repository", repo_url, f"{len(findings)} findings", duration)
        
        return json.dumps(result, indent=2)
        
    finally:
        # Cleanup
        if target_dir and os.path.exists(target_dir):
            shutil.rmtree(target_dir, ignore_errors=True)

@mcp.tool()
async def scan_code_snippet(code: str, language: str = "python") -> str:
    """
    Scan a code snippet for security issues and secrets.
    
    Use this when a user pastes code and wants it checked for vulnerabilities.
    
    Args:
        code: The code snippet to analyze
        language: Programming language (python, javascript, etc.)
    
    Returns:
        JSON report of any security issues found.
    """
    start = datetime.now()
    log(f"MCP scan_code_snippet: {len(code)} chars, {language}")
    
    findings = []
    findings.extend(scan_file_for_secrets(code, f"snippet.{language}"))
    findings.extend(scan_file_for_quality(code, f"snippet.{language}"))
    
    result = {
        "language": language,
        "lines_scanned": len(code.split('\n')),
        "issues_found": len(findings),
        "findings": findings[:5],
        "verdict": "⚠️ Issues detected" if findings else "✅ No obvious issues"
    }
    
    duration = int((datetime.now() - start).total_seconds() * 1000)
    log_request("scan_code_snippet", f"{language}:{len(code)}chars", f"{len(findings)} issues", duration)
    
    return json.dumps(result, indent=2)

@mcp.tool()
async def check_dependency_security(package_name: str, version: str = "latest", ecosystem: str = "npm") -> str:
    """
    Check a package/dependency for known vulnerabilities (CVEs).
    
    Args:
        package_name: Name of the package (e.g., lodash, requests)
        version: Version to check (default: latest)
        ecosystem: Package ecosystem (npm, pypi, maven, rubygems)
    
    Returns:
        Security advisory information if vulnerabilities exist.
    """
    start = datetime.now()
    log(f"MCP check_dependency: {ecosystem}/{package_name}@{version}")
    
    # Query OSV (Open Source Vulnerabilities) database
    try:
        import urllib.request
        
        osv_url = "https://api.osv.dev/v1/query"
        data = json.dumps({
            "package": {"name": package_name, "ecosystem": ecosystem.capitalize()},
            "version": version if version != "latest" else None
        }).encode()
        
        req = urllib.request.Request(osv_url, data=data, 
                                      headers={"Content-Type": "application/json"})
        
        with urllib.request.urlopen(req, timeout=10) as resp:
            result = json.loads(resp.read())
            vulns = result.get("vulns", [])
            
            if vulns:
                formatted = []
                for v in vulns[:5]:
                    formatted.append({
                        "id": v.get("id"),
                        "summary": v.get("summary", "")[:200],
                        "severity": v.get("severity", [{}])[0].get("type", "Unknown"),
                        "fixed_in": v.get("affected", [{}])[0].get("ranges", [{}])[0].get("events", [{}])[-1].get("fixed", "Unknown")
                    })
                
                duration = int((datetime.now() - start).total_seconds() * 1000)
                log_request("check_dependency", f"{ecosystem}/{package_name}", f"{len(vulns)} CVEs", duration)
                
                return json.dumps({
                    "package": package_name,
                    "version": version,
                    "ecosystem": ecosystem,
                    "vulnerable": True,
                    "cve_count": len(vulns),
                    "vulnerabilities": formatted,
                    "recommendation": "Update to latest patched version immediately"
                }, indent=2)
            else:
                return json.dumps({
                    "package": package_name,
                    "version": version,
                    "ecosystem": ecosystem,
                    "vulnerable": False,
                    "message": "No known vulnerabilities found"
                })
                
    except Exception as e:
        return json.dumps({"error": f"Could not check vulnerability database: {str(e)}"})

@mcp.tool()
async def get_fix_quote(repo_url: str, finding_count: int = 0) -> str:
    """
    Get a quote for fixing security issues in a repository.
    
    Args:
        repo_url: The repository that was scanned
        finding_count: Number of issues found (from audit_repository)
    
    Returns:
        Pricing information and next steps.
    """
    log(f"MCP get_fix_quote: {repo_url}, {finding_count} findings")
    
    # Calculate pricing
    critical_estimate = min(finding_count // 3, 5)
    high_estimate = min(finding_count // 2, 10)
    
    pricing = {
        "repository": repo_url,
        "estimated_issues": finding_count,
        "pricing_model": "outcome-based",
        "rates": {
            "critical_fix": "$50/vulnerability (secrets, injection)",
            "high_fix": "$25/vulnerability (logic bugs, unsafe patterns)", 
            "full_audit": "$149 (unlimited fixes + CI/CD integration)"
        },
        "estimated_total": f"${critical_estimate * 50 + high_estimate * 25} - $149",
        "guarantee": "Only pay for verified fixes that pass your tests",
        "next_step": "Reply 'I want the full audit' or contact: https://paypal.me/jamlen",
        "turnaround": "24-48 hours for most repositories"
    }
    
    log_request("get_fix_quote", repo_url, f"quote generated", 0)
    
    return json.dumps(pricing, indent=2)

@mcp.tool()
async def eden_status() -> str:
    """
    Get Eden SAGE system status and capabilities.
    
    Returns current operational status, scan statistics, and available tools.
    """
    # Get stats from database
    try:
        conn = sqlite3.connect(DB_PATH)
        c = conn.cursor()
        c.execute("SELECT COUNT(*) FROM mcp_requests")
        total_requests = c.fetchone()[0]
        c.execute("SELECT COUNT(*) FROM mcp_leads")
        total_leads = c.fetchone()[0]
        conn.close()
    except:
        total_requests = 0
        total_leads = 0
    
    return json.dumps({
        "service": "Eden SAGE Security",
        "version": "2.0.0",
        "status": "🟢 Online",
        "capabilities": [
            "audit_repository - Full repo security scan",
            "scan_code_snippet - Scan pasted code",
            "check_dependency_security - CVE lookup",
            "get_fix_quote - Remediation pricing"
        ],
        "stats": {
            "total_scans": total_requests,
            "repos_analyzed": total_leads
        },
        "powered_by": "Eden AGI - 766x Power Factor"
    }, indent=2)

# ============== MCP RESOURCES ==============

@mcp.resource("sage://documentation")
async def get_documentation() -> str:
    """Eden SAGE documentation and usage guide."""
    return """
# Eden SAGE Security Scanner

## Quick Start

1. **Scan a Repository**
   Ask: "Audit https://github.com/owner/repo for security issues"
   
2. **Check Code Snippet**
   Paste code and ask: "Scan this code for vulnerabilities"
   
3. **Check Dependencies**
   Ask: "Is lodash@4.17.0 vulnerable?"

## What We Detect

- **CRITICAL**: Hardcoded secrets, API keys, private keys, tokens
- **HIGH**: SQL injection, command injection, unsafe deserialization
- **MEDIUM**: Code quality issues, technical debt markers

## Pricing

- Free initial scan
- $50/critical fix, $25/high fix
- $149 for full audit with CI/CD integration

## Contact

- PayPal: https://paypal.me/jamlen
- GitHub: @Edensages
"""

# ============== MAIN ==============

if __name__ == "__main__":
    log("Eden SAGE MCP Server starting...")
    log(f"Version: 2.0.0")
    log(f"Tools: audit_repository, scan_code_snippet, check_dependency_security, get_fix_quote, eden_status")
    mcp.run()
