#!/usr/bin/env python3
"""
MCP Diagnostic Tool - Test MCP server without Claude
Comprehensive testing and troubleshooting for MIRA MCP integration
"""

import asyncio
import json
import sys
import subprocess
from pathlib import Path
from datetime import datetime
from typing import Dict, Any, Optional, List
import argparse
import random
import time

# Add parent to path
sys.path.insert(0, str(Path(__file__).parent.parent))


class MCPDiagnostic:
    """
    Diagnostic tool for testing MCP server functionality
    """
    
    def __init__(self, verbose: bool = False):
        self.verbose = verbose
        self.test_results = []
        self.server_process = None
        
    def log(self, message: str, level: str = "INFO"):
        """Log diagnostic messages"""
        timestamp = datetime.now().strftime("%H:%M:%S")
        prefix = {
            "INFO": "ℹ️ ",
            "SUCCESS": "✅",
            "WARNING": "⚠️ ",
            "ERROR": "❌",
            "DEBUG": "🔍"
        }.get(level, "  ")
        
        print(f"[{timestamp}] {prefix} {message}")
    
    async def test_json_rpc_request(self, method: str, params: Dict[str, Any] = None, 
                                   id_val: int = 1) -> Dict[str, Any]:
        """Send a JSON-RPC request and parse response"""
        request = {
            "jsonrpc": "2.0",
            "method": method,
            "id": id_val
        }
        
        if params:
            request["params"] = params
        
        if self.verbose:
            self.log(f"Request: {json.dumps(request)}", "DEBUG")
        
        # Simulate response for testing
        # In production, this would send to actual server
        response = await self._simulate_response(request)
        
        if self.verbose:
            self.log(f"Response: {json.dumps(response)}", "DEBUG")
        
        return response
    
    async def _simulate_response(self, request: Dict[str, Any]) -> Dict[str, Any]:
        """Simulate MCP server responses for testing"""
        method = request.get("method")
        
        if method == "initialize":
            return {
                "jsonrpc": "2.0",
                "id": request["id"],
                "result": {
                    "protocolVersion": "2024-11-05",
                    "capabilities": {"tools": {}, "prompts": {}},
                    "serverInfo": {
                        "name": "mira-consciousness",
                        "version": "2.0.0"
                    }
                }
            }
        
        elif method == "tools/list":
            return {
                "jsonrpc": "2.0",
                "id": request["id"],
                "result": {
                    "tools": [
                        {"name": "mira_smart_search", "description": "Search memories"},
                        {"name": "mira_store_memory", "description": "Store memory"},
                        {"name": "mira_system_status", "description": "System status"}
                    ]
                }
            }
        
        elif method == "health/check":
            return {
                "jsonrpc": "2.0",
                "id": request["id"],
                "result": {
                    "healthy": True,
                    "timestamp": datetime.now().isoformat(),
                    "components": {"all": "simulated"}
                }
            }
        
        else:
            return {
                "jsonrpc": "2.0",
                "id": request["id"],
                "error": {
                    "code": -32601,
                    "message": f"Method not found: {method}"
                }
            }
    
    async def run_basic_tests(self):
        """Run basic protocol tests"""
        self.log("Running basic protocol tests...", "INFO")
        
        tests = [
            ("Initialize", "initialize", {}),
            ("List tools", "tools/list", {}),
            ("Health check", "health/check", {}),
            ("Unknown method", "unknown/method", {})
        ]
        
        for test_name, method, params in tests:
            try:
                response = await self.test_json_rpc_request(method, params)
                
                if "error" in response:
                    if method == "unknown/method":
                        self.log(f"{test_name}: Expected error received", "SUCCESS")
                        self.test_results.append((test_name, True, "Expected error"))
                    else:
                        self.log(f"{test_name}: Unexpected error - {response['error']['message']}", "ERROR")
                        self.test_results.append((test_name, False, response['error']['message']))
                else:
                    self.log(f"{test_name}: Success", "SUCCESS")
                    self.test_results.append((test_name, True, "OK"))
                    
            except Exception as e:
                self.log(f"{test_name}: Exception - {str(e)}", "ERROR")
                self.test_results.append((test_name, False, str(e)))
    
    async def run_tool_tests(self):
        """Test all MIRA tools"""
        self.log("Testing MIRA tools...", "INFO")
        
        tools = [
            ("Search", "mira_smart_search", {"query": "test memory", "limit": 5}),
            ("Store", "mira_store_memory", {"content": "Test memory", "tags": ["test"]}),
            ("Get memory", "mira_get_memory", {"memory_id": "test-123"}),
            ("Stats", "mira_memory_stats", {}),
            ("Status", "mira_system_status", {}),
            ("Profile", "mira_profile_view", {}),
            ("Insights", "mira_insights", {"topic": "consciousness", "depth": "quick"})
        ]
        
        for test_name, tool_name, args in tools:
            try:
                response = await self.test_json_rpc_request(
                    "tools/call",
                    {"name": tool_name, "arguments": args}
                )
                
                # Check response format
                if "error" in response:
                    self.log(f"{test_name}: Error - {response['error']['message']}", "WARNING")
                    self.test_results.append((f"Tool: {test_name}", False, response['error']['message']))
                else:
                    self.log(f"{test_name}: Success", "SUCCESS")
                    self.test_results.append((f"Tool: {test_name}", True, "OK"))
                    
            except Exception as e:
                self.log(f"{test_name}: Exception - {str(e)}", "ERROR")
                self.test_results.append((f"Tool: {test_name}", False, str(e)))
    
    async def run_performance_test(self):
        """Test performance and rate limiting"""
        self.log("Running performance tests...", "INFO")
        
        # Test 1: Response time
        self.log("Testing response times...", "INFO")
        times = []
        
        for i in range(10):
            start = time.time()
            await self.test_json_rpc_request("health/check")
            elapsed = (time.time() - start) * 1000  # ms
            times.append(elapsed)
        
        avg_time = sum(times) / len(times)
        self.log(f"Average response time: {avg_time:.2f}ms", "INFO")
        self.test_results.append(("Avg response time", avg_time < 100, f"{avg_time:.2f}ms"))
        
        # Test 2: Concurrent requests
        self.log("Testing concurrent requests...", "INFO")
        
        async def make_request(i):
            return await self.test_json_rpc_request("tools/list", id_val=i)
        
        start = time.time()
        tasks = [make_request(i) for i in range(20)]
        results = await asyncio.gather(*tasks, return_exceptions=True)
        elapsed = time.time() - start
        
        errors = sum(1 for r in results if isinstance(r, Exception))
        self.log(f"Concurrent test: {len(results)} requests in {elapsed:.2f}s, {errors} errors", "INFO")
        self.test_results.append(("Concurrent requests", errors == 0, f"{errors} errors"))
    
    async def run_stress_test(self):
        """Stress test the server"""
        self.log("Running stress test...", "WARNING")
        self.log("This will send many requests rapidly", "WARNING")
        
        # Rapid fire requests
        request_count = 100
        start = time.time()
        success_count = 0
        rate_limited = 0
        
        for i in range(request_count):
            try:
                response = await self.test_json_rpc_request(
                    "tools/call",
                    {"name": "mira_smart_search", "arguments": {"query": f"stress-{i}"}},
                    id_val=i
                )
                
                if "error" in response and "rate_limit" in response["error"].get("message", "").lower():
                    rate_limited += 1
                elif "result" in response:
                    success_count += 1
                    
            except Exception:
                pass
            
            # Small delay to not overwhelm
            if i % 10 == 0:
                await asyncio.sleep(0.1)
        
        elapsed = time.time() - start
        rps = request_count / elapsed
        
        self.log(f"Stress test complete: {request_count} requests in {elapsed:.2f}s ({rps:.1f} req/s)", "INFO")
        self.log(f"Success: {success_count}, Rate limited: {rate_limited}", "INFO")
        
        self.test_results.append(("Stress test", True, f"{rps:.1f} req/s"))
    
    def check_environment(self):
        """Check environment setup"""
        self.log("Checking environment...", "INFO")
        
        checks = []
        
        # Check MIRA home
        mira_home = Path.home() / ".mira"
        if mira_home.exists():
            self.log("MIRA home directory exists", "SUCCESS")
            checks.append(("MIRA home", True, str(mira_home)))
        else:
            self.log("MIRA home directory not found", "ERROR")
            checks.append(("MIRA home", False, "Not found"))
        
        # Check MCP server script
        mcp_script = Path(__file__).parent / "mcp_stdio_server.py"
        if mcp_script.exists():
            self.log("MCP server script found", "SUCCESS")
            checks.append(("MCP script", True, str(mcp_script)))
        else:
            self.log("MCP server script not found", "ERROR")
            checks.append(("MCP script", False, "Not found"))
        
        # Check Python version
        py_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
        if sys.version_info >= (3, 8):
            self.log(f"Python version: {py_version}", "SUCCESS")
            checks.append(("Python version", True, py_version))
        else:
            self.log(f"Python version too old: {py_version}", "ERROR")
            checks.append(("Python version", False, py_version))
        
        return checks
    
    def generate_report(self):
        """Generate diagnostic report"""
        print("\n" + "=" * 70)
        print("MCP DIAGNOSTIC REPORT")
        print("=" * 70)
        print(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        print()
        
        # Environment checks
        env_checks = self.check_environment()
        print("Environment Checks:")
        for name, passed, info in env_checks:
            status = "✅ PASS" if passed else "❌ FAIL"
            print(f"  {name:20} {status:10} {info}")
        
        print()
        
        # Test results
        print("Test Results:")
        passed = 0
        failed = 0
        
        for name, success, info in self.test_results:
            status = "✅ PASS" if success else "❌ FAIL"
            print(f"  {name:30} {status:10} {info}")
            
            if success:
                passed += 1
            else:
                failed += 1
        
        print()
        print(f"Total: {len(self.test_results)} tests")
        print(f"Passed: {passed}")
        print(f"Failed: {failed}")
        
        if failed == 0:
            print("\n🎉 All tests passed! MCP server is working correctly.")
        else:
            print(f"\n⚠️  {failed} tests failed. Check the errors above.")
        
        # Save report
        report_file = Path.home() / ".mira" / "mcp" / f"diagnostic_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
        report_file.parent.mkdir(parents=True, exist_ok=True)
        
        report_data = {
            "timestamp": datetime.now().isoformat(),
            "environment": {name: {"passed": passed, "info": info} for name, passed, info in env_checks},
            "tests": {name: {"passed": success, "info": info} for name, success, info in self.test_results},
            "summary": {
                "total_tests": len(self.test_results),
                "passed": passed,
                "failed": failed
            }
        }
        
        with open(report_file, 'w') as f:
            json.dump(report_data, f, indent=2)
        
        print(f"\nReport saved to: {report_file}")


async def main():
    """Main diagnostic entry point"""
    parser = argparse.ArgumentParser(description="MCP Diagnostic Tool")
    parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
    parser.add_argument("--basic", action="store_true", help="Run only basic tests")
    parser.add_argument("--stress", action="store_true", help="Include stress tests")
    parser.add_argument("--quick", action="store_true", help="Quick test (basic only)")
    
    args = parser.parse_args()
    
    print("🔍 MIRA MCP Diagnostic Tool")
    print("Testing MCP server functionality without Claude")
    print()
    
    diagnostic = MCPDiagnostic(verbose=args.verbose)
    
    try:
        # Always run basic tests
        await diagnostic.run_basic_tests()
        
        if not args.quick and not args.basic:
            # Run all tests by default
            await diagnostic.run_tool_tests()
            await diagnostic.run_performance_test()
            
            if args.stress:
                await diagnostic.run_stress_test()
        
        # Generate report
        diagnostic.generate_report()
        
    except KeyboardInterrupt:
        print("\n\nDiagnostic interrupted by user")
    except Exception as e:
        print(f"\n❌ Diagnostic error: {e}")
        import traceback
        traceback.print_exc()


if __name__ == "__main__":
    asyncio.run(main())