#!/usr/bin/env python3
"""Nexus MCP Server — 测试/运维工具集 (mcp 1.x API)

All paths read from NEXUS_DEPLOY_DIR env var (default: /opt/nexus).
"""
import json, os, re, subprocess
from mcp.server.fastmcp import FastMCP

API_BASE = "http://127.0.0.1:8600"
API_KEY = ""
DEPLOY_DIR = os.environ.get("NEXUS_DEPLOY_DIR", "/opt/nexus")

def _load_db_creds():
    """Load DB credentials from .env file (never hardcode)"""
    env_path = os.path.join(DEPLOY_DIR, ".env")
    creds = {"user": "nexus", "pass": "", "db": "nexus"}
    try:
        if os.path.exists(env_path):
            with open(env_path) as f:
                for line in f:
                    line = line.strip()
                    if line.startswith("NEXUS_DATABASE_URL="):
                        url = line.split("=", 1)[1].strip()
                        m = re.match(r"mysql\+pymysql://([^:]+):([^@]+)@([^/]+)/(.+)", url)
                        if m:
                            creds["user"] = m.group(1)
                            creds["pass"] = m.group(2)
                            creds["db"] = m.group(4)
    except Exception:
        pass
    return creds

server = FastMCP("nexus")

# ================================================================
# T1: API 调用
# ================================================================
@server.tool()
async def api_call(method: str = "GET", path: str = "/health", body: str = "{}"):
    """调用 Nexus Python API。method: GET/POST/PUT/DELETE, path: /api/servers/等, body: JSON字符串"""
    import urllib.request
    url = f"{API_BASE}{path}"
    data = body.encode() if method in ("POST","PUT") else None
    req = urllib.request.Request(url, data=data, method=method)
    req.add_header("Content-Type", "application/json")
    if API_KEY: req.add_header("X-API-Key", API_KEY)
    try:
        resp = urllib.request.urlopen(req, timeout=10)
        return json.dumps(json.loads(resp.read()), indent=2, ensure_ascii=False)
    except Exception as e:
        return str(e)

# ================================================================
# T2: 数据库查询 (read-only, credentials from .env)
# ================================================================
@server.tool()
async def db_query(sql: str = "SELECT COUNT(*) FROM servers"):
    """直查 MySQL nexus 数据库。只读查询"""
    sql_upper = sql.strip().upper()
    if not sql_upper.startswith("SELECT") and not sql_upper.startswith("SHOW") and not sql_upper.startswith("DESCRIBE"):
        return "Error: Only SELECT/SHOW/DESCRIBE queries allowed"
    if ";" in sql.rstrip(";").strip() and len(sql.rstrip(";").strip().split(";")) > 1:
        return "Error: Multiple statements not allowed"
    try:
        creds = _load_db_creds()
        p = subprocess.run(
            ['mysql', f'-u{creds["user"]}', f'-p{creds["pass"]}', creds["db"], '-e', sql],
            capture_output=True, text=True, timeout=10
        )
        return p.stdout if p.returncode == 0 else p.stderr
    except Exception as e:
        return str(e)

# ================================================================
# T3: 服务管理
# ================================================================
@server.tool()
async def service(action: str = "status"):
    """管理 Python 后端服务。action: status/start/stop/restart"""
    try:
        p = subprocess.run(['supervisorctl', action, 'nexus'], capture_output=True, text=True, timeout=15)
        return p.stdout or p.stderr or f"nexus {action}: OK"
    except Exception as e:
        return str(e)

# ================================================================
# T4: 日志查看
# ================================================================
@server.tool()
async def logs(lines: int = 50):
    """查看 Python 后端日志。lines: 行数"""
    try:
        log_path = os.path.join(DEPLOY_DIR, "logs", "nexus.log")
        p = subprocess.run(['tail', '-n', str(lines), log_path],
                           capture_output=True, text=True, timeout=10)
        return p.stdout[-3000:]
    except Exception as e:
        return str(e)

# ================================================================
# T5: 运行测试
# ================================================================
@server.tool()
async def run_test():
    """运行 test_api.py 端到端测试"""
    try:
        p = subprocess.run(['python3', os.path.join(DEPLOY_DIR, 'tests', 'test_api.py')],
                           capture_output=True, text=True, timeout=60,
                           cwd=DEPLOY_DIR)
        return p.stdout
    except Exception as e:
        return str(e)

# ================================================================
# T6: Git 日志
# ================================================================
@server.tool()
async def git_log(count: int = 10):
    """查看 project Git 提交历史"""
    try:
        p = subprocess.run(['git', 'log', f'-{count}', '--oneline'],
                           capture_output=True, text=True, timeout=5,
                           cwd=DEPLOY_DIR)
        return p.stdout
    except Exception as e:
        return str(e)

# ================================================================
# T7: 部署同步
# ================================================================
@server.tool()
async def deploy():
    """同步最新代码到运行目录并重启服务（需通过 pre-deploy 门控检查）"""
    # ── Pull latest code first ──
    try:
        pull = subprocess.run(
            f"cd {DEPLOY_DIR} && git fetch --all 2>&1 && git reset --hard origin/main 2>&1",
            shell=True, capture_output=True, text=True, timeout=30,
        )
        if pull.returncode != 0:
            return f"🚫 Git pull failed:\n\n{pull.stdout}\n{pull.stderr}"
    except Exception as e:
        return f"🚫 Git pull error: {e}"

    # ── Gate Check (after pull so files are up to date) ──
    gate_script = os.path.join(DEPLOY_DIR, "deploy", "pre_deploy_check.sh")
    if os.path.exists(gate_script):
        try:
            g = subprocess.run(
                ['bash', gate_script],
                capture_output=True, text=True, timeout=30,
                env={**os.environ, "NEXUS_DEPLOY_DIR": DEPLOY_DIR},
            )
            if g.returncode != 0:
                return f"🚫 DEPLOY BLOCKED — Pre-deploy gate check failed:\n\n{g.stdout}\n\nFix the blocked gates before deploying."
        except Exception as e:
            return f"🚫 Gate check error: {e}"
    else:
        # Gate script not on server yet — warn but allow (bootstrap scenario)
        pass

    # ── Restart service ──
    try:
        p = subprocess.run("supervisorctl restart nexus 2>&1", shell=True, capture_output=True, text=True, timeout=30)
        return f"Pull:\n{pull.stdout}\n---\nRestart:\n{p.stdout or p.stderr or 'OK'}"
    except Exception as e:
        return str(e)

# ================================================================
# T8: 配置查看
# ================================================================
@server.tool()
async def config_view():
    """查看当前非敏感配置（隐藏密码/密钥）"""
    try:
        with open(os.path.join(DEPLOY_DIR, '.env')) as f:
            lines = []
            for line in f:
                line = line.strip()
                if not line or line.startswith('#'):
                    lines.append(line)
                    continue
                for sensitive in ('DATABASE_URL', 'SECRET_KEY', 'API_KEY', 'ADMIN_PASSWORD',
                                  'TELEGRAM_BOT_TOKEN', 'TELEGRAM_CHAT_ID', 'ENCRYPTION_KEY'):
                    if line.startswith(f'NEXUS_{sensitive}='):
                        key = line.split('=', 1)[0]
                        lines.append(f'{key}=***REDACTED***')
                        break
                else:
                    lines.append(line)
            return '\n'.join(lines)
    except Exception as e:
        return str(e)

# ================================================================
# T9: 门控日志
# ================================================================
@server.tool()
async def gate_log(lines: int = 20):
    """查看门控执行日志。lines: 显示最近N条记录"""
    try:
        log_path = os.path.join(DEPLOY_DIR, "deploy", "gate_log.jsonl")
        if not os.path.exists(log_path):
            return "No gate log found — no gate checks have been recorded yet"
        result = []
        with open(log_path, encoding="utf-8") as f:
            all_lines = f.readlines()
        for line in all_lines[-lines:]:
            d = json.loads(line)
            icon = {"PASS": "✅", "BLOCK": "🚫", "SKIP": "⏭️", "WARN": "⚠️"}.get(d["result"], "?")
            ts = d.get("ts", "")[:19]
            gate = d.get("gate", "?")
            detail = d.get("detail", "")
            result.append(f"{ts} {icon} {gate}: {detail}")
        return "\n".join(result) if result else "No entries"
    except Exception as e:
        return str(e)

# ================================================================
# Main
# ================================================================
if __name__ == "__main__":
    import asyncio
    asyncio.run(server.run_stdio_async())
