import json
import subprocess
import os
import sys
import threading
import time
from typing import Dict, Any, List, Optional

class GHLMCPBridge:
    """
    Bridges Genesis Python environment to the GHL MCP Node.js server.
    Implements standard MCP JSON-RPC over stdio.
    """
    
    def __init__(self, server_dir: str, env: Dict[str, str] = None):
        self.server_dir = server_dir
        self.process = None
        self.request_id = 1
        self.pending_requests = {}
        self.initialized = False
        self.tools = []
        
        # Load environment variables from .env in server_dir
        # Standardize for Windows: keys must be uppercase, no nulls
        self.env = {}
        for k, v in os.environ.items():
            if v is not None:
                clean_k = k.replace('\0', '')
                clean_v = v.replace('\0', '')
                self.env[clean_k] = clean_v
                
        env_path = os.path.join(server_dir, ".env")
        if os.path.exists(env_path):
            with open(env_path, "r") as f:
                for line in f:
                    line = line.strip()
                    if "=" in line and not line.startswith("#"):
                        parts = line.split("=", 1)
                        if len(parts) == 2:
                            key, val = parts
                            clean_k = key.strip().replace('\0', '')
                            clean_v = val.strip().strip('"').strip("'").replace('\0', '')
                            self.env[clean_k] = clean_v
        
        # Merge explicitly passed env
        if env:
            self.env.update(env)

    def _start_server(self):
        """Starts the MCP server as a subprocess."""
        # Absolute paths for Windows stability
        node_path = "node"
        js_path = os.path.join(self.server_dir, "dist", "server.js").replace('\0', '')
        cmd = [node_path, js_path]
        
        print(f"📡 [Bridge] Starting GHL Server: {' '.join(cmd)}")
        try:
            self.process = subprocess.Popen(
                cmd,
                cwd=self.server_dir.replace('\0', ''),
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                env={k: str(v) for k, v in self.env.items()}, # Ensure all strings
                bufsize=1,
                encoding='utf-8'
            )
        except ValueError as e:
            print(f"❌ [Bridge] Subprocess ValueError: {e}")
            # Identify the culprit
            for k, v in self.env.items():
                if '\0' in k or '\0' in str(v):
                    print(f"   [Culprit] ENV {k}: (has null)")
            raise e
        
        # Start stderr reader thread to log server messages
        threading.Thread(target=self._read_stderr, daemon=True).start()
        # Start stdout reader thread to handle JSON-RPC responses
        threading.Thread(target=self._read_stdout, daemon=True).start()

    def _read_stderr(self):
        for line in self.process.stderr:
            print(f"[GHL-MCP-LOG] {line.strip()}", file=sys.stderr)

    def _read_stdout(self):
        for line in self.process.stdout:
            try:
                msg = json.loads(line)
                if "id" in msg:
                    req_id = msg["id"]
                    if req_id in self.pending_requests:
                        self.pending_requests[req_id] = msg
                elif "method" in msg:
                    # Handle notifications or server-side requests
                    pass
            except json.JSONDecodeError:
                pass

    def _send_request(self, method: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
        if not self.process:
            self._start_server()
            # Perform handshake
            self._send_request("initialize", {
                "protocolVersion": "2024-11-05",
                "capabilities": {},
                "clientInfo": {"name": "genesis-bridge", "version": "1.0.0"}
            })
            self._send_notification("notifications/initialized")
            self.initialized = True

        req_id = self.request_id
        self.request_id += 1
        
        request = {
            "jsonrpc": "2.0",
            "id": req_id,
            "method": method,
            "params": params or {}
        }
        
        self.pending_requests[req_id] = None
        self.process.stdin.write(json.dumps(request) + "\n")
        self.process.stdin.flush()
        
        # Wait for response
        start_time = time.time()
        while self.pending_requests[req_id] is None:
            if time.time() - start_time > 30: # 30s timeout
                return {"error": {"code": -32000, "message": "Request timeout"}}
            time.sleep(0.1)
            
        return self.pending_requests.pop(req_id)

    def _send_notification(self, method: str, params: Dict[str, Any] = None):
        notification = {
            "jsonrpc": "2.0",
            "method": method,
            "params": params or {}
        }
        self.process.stdin.write(json.dumps(notification) + "\n")
        self.process.stdin.flush()

    def list_tools(self) -> List[Dict[str, Any]]:
        response = self._send_request("tools/list")
        if "result" in response:
            self.tools = response["result"].get("tools", [])
            return self.tools
        return []

    def call_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
        response = self._send_request("tools/call", {
            "name": tool_name,
            "arguments": arguments
        })
        return response

    def stop(self):
        if self.process:
            self.process.terminate()
            self.process = None

if __name__ == "__main__":
    # Self-test logic
    bridge = GHLMCPBridge(r"E:\genesis-system\mcp-servers\ghl")
    try:
        print("[TEST] Fetching tools...")
        tools = bridge.list_tools()
        print(f"[TEST] Found {len(tools)} tools.")
        if tools:
            print(f"[TEST] First tool: {tools[0]['name']}")
    finally:
        bridge.stop()
