#!/usr/bin/env python3
"""Test script to verify robust OpenClaw installation with Node.js fallback."""

import sys
import os
from pathlib import Path
from unittest.mock import patch, MagicMock

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

from openclaw_installer.installer import OpenClawInstaller
from openclaw_installer.config import Config

def test_robust_install_with_fallback():
    """Test that installation retries with Node.js download on failure."""
    print("Testing robust install with Node.js fallback...")
    print("=" * 60)
    
    config = Config()
    installer = OpenClawInstaller(config, dry_run=False)
    
    # Simulate broken npm on first attempt
    attempt_count = 0
    
    def mock_subprocess_run(*args, **kwargs):
        nonlocal attempt_count
        attempt_count += 1
        
        cmd = args[0] if args else kwargs.get('args', [])
        
        if 'npm' in str(cmd):
            if attempt_count == 1:
                # First attempt: simulate broken npm
                print("[Mock] First npm attempt failed (simulating broken npm)")
                result = MagicMock()
                result.returncode = 1
                result.stdout = ""
                result.stderr = "Error: Cannot find module '../lib/cli.js'"
                return result
            else:
                # Second/third attempt with downloaded Node.js: success
                print(f"[Mock] npm attempt {attempt_count} succeeded")
                result = MagicMock()
                result.returncode = 0
                result.stdout = "+ openclaw@1.0.0"
                result.stderr = ""
                return result
        return MagicMock(returncode=0)
    
    def mock_download_node(*args, **kwargs):
        print("[Mock] Node.js download succeeded")
        return True
    
    with patch('subprocess.run', side_effect=mock_subprocess_run):
        with patch('openclaw_installer.installer.download_node', side_effect=mock_download_node):
            # Mock shutil.rmtree to avoid actually deleting anything
            with patch('shutil.rmtree'):
                # Set up initial broken node_bins
                installer.node_bins = {
                    "node": Path("/usr/bin/node"),
                    "npm": Path("/usr/bin/npm"),
                    "source": "system",
                }
                
                result = installer._install_openclaw()
    
    print(f"\nTest result: {'PASSED' if result and attempt_count >= 2 else 'FAILED'}")
    print(f"Total attempts: {attempt_count}")
    print(f"Node source after test: {installer.node_bins.get('source')}")
    
    return result


def test_robust_install_direct_success():
    """Test successful installation on first attempt."""
    print("\n\nTesting direct successful install...")
    print("=" * 60)
    
    config = Config()
    installer = OpenClawInstaller(config, dry_run=False)
    
    with patch('subprocess.run', return_value=MagicMock(returncode=0)):
        installer.node_bins = {
            "node": Path("/usr/bin/node"),
            "npm": Path("/usr/bin/npm"),
            "source": "system",
        }
        
        result = installer._install_openclaw()
    
    print(f"\nTest result: {'PASSED' if result else 'FAILED'}")
    
    return result


def test_robust_install_all_failures():
    """Test complete failure after all attempts."""
    print("\n\nTesting complete failure scenario...")
    print("=" * 60)
    
    config = Config()
    installer = OpenClawInstaller(config, dry_run=False)
    
    def mock_subprocess_run(*args, **kwargs):
        result = MagicMock()
        result.returncode = 1
        result.stdout = ""
        result.stderr = "npm ERR! code ECONNREFUSED"
        return result
    
    def mock_download_node(*args, **kwargs):
        print("[Mock] Node.js download succeeded")
        return True
    
    with patch('subprocess.run', side_effect=mock_subprocess_run):
        with patch('openclaw_installer.installer.download_node', side_effect=mock_download_node):
            with patch('shutil.rmtree'):
                result = installer._install_openclaw()
    
    print(f"\nTest result: {'PASSED' if not result else 'FAILED'} (should fail)")
    
    return not result  # Should return False


def test_robust_install_timeout_retry():
    """Test timeout handling with retry."""
    print("\n\nTesting timeout and retry behavior...")
    print("=" * 60)
    
    config = Config()
    installer = OpenClawInstaller(config, dry_run=False)
    
    attempt_count = 0
    
    def mock_subprocess_run(*args, **kwargs):
        nonlocal attempt_count
        attempt_count += 1
        
        if attempt_count == 1:
            raise subprocess.TimeoutExpired(cmd=args[0], timeout=300)
        else:
            result = MagicMock()
            result.returncode = 0
            return result
    
    def mock_download_node(*args, **kwargs):
        print("[Mock] Node.js download succeeded")
        return True
    
    with patch('subprocess.run', side_effect=mock_subprocess_run):
        with patch('openclaw_installer.installer.download_node', side_effect=mock_download_node):
            with patch('shutil.rmtree'):
                installer.node_bins = {
                    "node": Path("/usr/bin/node"),
                    "npm": Path("/usr/bin/npm"),
                    "source": "system",
                }
                result = installer._install_openclaw()
    
    print(f"\nTest result: {'PASSED' if result and attempt_count >= 2 else 'FAILED'}")
    print(f"Total attempts: {attempt_count}")
    
    return result


def test_windows_path_configuration():
    """Test Windows-specific PATH handling."""
    print("\n\nTesting Windows PATH configuration...")
    print("=" * 60)
    
    import platform
    if platform.system() != "Windows":
        print("[Skipped] Not running on Windows")
        return True
    
    config = Config()
    installer = OpenClawInstaller(config, dry_run=False)
    
    captured_env = {}
    
    def mock_subprocess_run(*args, **kwargs):
        # Capture the environment passed to subprocess
        captured_env.update(kwargs.get('env', {}))
        result = MagicMock()
        result.returncode = 0
        return result
    
    with patch('subprocess.run', side_effect=mock_subprocess_run):
        installer.node_bins = {
            "node": Path("C:/openclaw-installer/node/node.exe"),
            "npm": Path("C:/openclaw-installer/node/npm.cmd"),
            "source": "downloaded",
        }
        result = installer._install_openclaw()
    
    # Verify PATH contains node directory
    path_env = captured_env.get('PATH', '')
    node_dir = str(Path("C:/openclaw-installer/node"))
    
    path_configured = node_dir in path_env
    
    print(f"Node directory in PATH: {path_configured}")
    print(f"PATH starts with node directory: {path_env.startswith(node_dir)}")
    print(f"\nTest result: {'PASSED' if result and path_configured else 'FAILED'}")
    
    return result and path_configured


def main():
    """Run all tests."""
    print("OpenClaw Robust Installer Tests")
    print("================================\n")
    
    tests = [
        ("Fallback to Node.js download", test_robust_install_with_fallback),
        ("Direct success", test_robust_install_direct_success),
        ("Complete failure", test_robust_install_all_failures),
        ("Timeout and retry", test_robust_install_timeout_retry),
        ("Windows PATH configuration", test_windows_path_configuration),
    ]
    
    results = []
    for test_name, test_func in tests:
        print(f"\n{'=' * 70}")
        print(f"Running: {test_name}")
        print(f"{'=' * 70}")
        try:
            result = test_func()
            results.append((test_name, result))
        except Exception as e:
            print(f"\nTest raised exception: {e}")
            import traceback
            traceback.print_exc()
            results.append((test_name, False))
    
    # Summary
    print("\n\n" + "=" * 70)
    print("TEST SUMMARY")
    print("=" * 70)
    for test_name, result in results:
        status = "PASSED" if result else "FAILED"
        color = "\033[92m" if result else "\033[91m"
        reset = "\033[0m"
        print(f"{color}{status:8}{reset} - {test_name}")
    
    passed = sum(1 for _, result in results if result)
    total = len(results)
    
    print(f"\nResults: {passed}/{total} tests passed")
    
    if passed == total:
        print("\n\033[92m✓ All tests passed!\033[0m")
        return 0
    else:
        print(f"\n\033[91m✗ {total - passed} test(s) failed\033[0m")
        return 1


if __name__ == "__main__":
    sys.exit(main())
