#!/usr/bin/env python3
"""Interactive E2E test script for multi-node mesh validation.

This script provides an interactive walkthrough of the E2E test scenarios,
useful for initial validation before running automated tests.

Usage:
    python scripts/e2e_manual_test.py [--hub-only] [--rpc DEST_HASH]

Options:
    --hub-only     Only test hub connectivity, skip node tests
    --rpc HASH     Send RPC status request to specific destination

Example:
    # Full interactive test
    python scripts/e2e_manual_test.py

    # Just test hub connection
    python scripts/e2e_manual_test.py --hub-only

    # Test RPC to specific node
    python scripts/e2e_manual_test.py --rpc 698f2232d4ddab456ca11f38c8bb8a90
"""

import argparse
import asyncio
import logging
import sys
import time
from pathlib import Path

# Add project root to path
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger("e2e_manual")


def print_header(text: str) -> None:
    """Print formatted section header."""
    print()
    print("=" * 60)
    print(f"  {text}")
    print("=" * 60)
    print()


def print_result(success: bool, message: str) -> None:
    """Print test result with indicator."""
    indicator = "[PASS]" if success else "[FAIL]"
    print(f"  {indicator} {message}")


async def test_hub_connectivity() -> bool:
    """Test Phase 1.1: Hub Connectivity."""
    print_header("Phase 1.1: Hub Connectivity")

    from styrene.services.app_lifecycle import StyreneLifecycle
    from styrene.services.config import load_config
    from styrene.services.hub_connection import HubStatus, get_hub_connection

    config = load_config()
    hub_address = config.reticulum.hub_address

    print(f"  Hub address: {hub_address}")
    print(f"  Hub enabled: {config.reticulum.hub_enabled}")

    if not config.reticulum.hub_enabled:
        config.reticulum.hub_enabled = True
        print("  (Enabled hub for test)")

    lifecycle = StyreneLifecycle(config)

    try:
        print("\n  Initializing Styrene services...")
        if not lifecycle.initialize():
            print_result(False, "Failed to initialize services")
            return False
        print_result(True, "Services initialized")

        # Wait for hub connection
        hub_conn = get_hub_connection()
        if hub_conn is None:
            print_result(False, "HubConnection not created")
            return False

        print(f"\n  Waiting for hub connection (up to 90s)...")
        print(f"  Initial status: {hub_conn.status.value}")

        start = time.time()
        timeout = 90
        last_status = None

        while (time.time() - start) < timeout:
            status = hub_conn.status

            if status != last_status:
                elapsed = time.time() - start
                print(f"  [{elapsed:5.1f}s] Status: {status.value}")
                last_status = status

            if status == HubStatus.CONNECTED:
                print_result(True, f"Connected to hub in {time.time() - start:.1f}s")
                return True

            await asyncio.sleep(1)

        print_result(False, f"Hub connection timeout after {timeout}s")
        return False

    finally:
        lifecycle.shutdown()


async def test_node_discovery(lifecycle) -> list[dict]:
    """Test Phase 1.2: Node Discovery."""
    print_header("Phase 1.2: Node Discovery")

    from styrene.services.node_store import get_node_store
    from styrene.services.reticulum import discover_devices

    # Check NodeStore for persisted nodes
    store = get_node_store()
    persisted = store.get_all_nodes()
    print(f"  Persisted nodes in store: {len(persisted)}")
    for node in persisted[:5]:  # Show first 5
        print(f"    - {node.name} ({node.identity_short}...)")

    # Wait for live discovery
    print("\n  Waiting for live announces (60s)...")

    discovered = []
    start = time.time()
    timeout = 60
    seen = set()

    while (time.time() - start) < timeout:
        devices = discover_devices()

        for device in devices:
            if device.destination_hash not in seen:
                seen.add(device.destination_hash)
                elapsed = time.time() - start
                print(
                    f"  [{elapsed:5.1f}s] Discovered: {device.name} ({device.identity_short}...)"
                )
                print(f"           Type: {device.device_type.value}")
                print(f"           Identity hash: {device.identity_hash[:16]}...")
                discovered.append(
                    {
                        "name": device.name,
                        "destination_hash": device.destination_hash,
                        "identity_hash": device.identity_hash,
                        "device_type": device.device_type.value,
                    }
                )

        await asyncio.sleep(2)

    print(f"\n  Total discovered: {len(discovered)}")
    print_result(len(discovered) > 0, f"Discovered {len(discovered)} nodes")

    return discovered


async def test_rpc_roundtrip(destination: str, lifecycle) -> bool:
    """Test Phase 2.1: RPC Status Request."""
    print_header("Phase 2.1: RPC Status Request")

    from styrene.services.lxmf_service import get_lxmf_service
    from styrene.services.rpc_client import RPCClient

    print(f"  Destination: {destination}")

    lxmf_service = get_lxmf_service()
    if lxmf_service is None:
        print_result(False, "LXMF service not available")
        return False

    rpc_client = RPCClient(lxmf_service)

    print("\n  Sending status_request...")
    start = time.time()

    try:
        response = await rpc_client.call_status(destination, timeout=30.0)
        elapsed = time.time() - start

        print_result(True, f"Response received in {elapsed:.2f}s")
        print(f"\n  === STATUS RESPONSE ===")
        print(f"  Uptime: {response.format_uptime()}")
        print(f"  IP: {response.ip}")
        print(f"  Disk: {response.format_disk_usage()}")
        print(f"  Services: {response.services}")

        return True

    except Exception as e:
        elapsed = time.time() - start
        print_result(False, f"RPC failed after {elapsed:.2f}s: {e}")
        return False


async def test_exec_command(destination: str, lifecycle) -> bool:
    """Test Phase 2.2: Command Execution."""
    print_header("Phase 2.2: Command Execution")

    from styrene.services.lxmf_service import get_lxmf_service
    from styrene.services.rpc_client import RPCClient

    print(f"  Destination: {destination}")
    print(f"  Command: uname -a")

    lxmf_service = get_lxmf_service()
    rpc_client = RPCClient(lxmf_service)

    print("\n  Sending exec request...")
    start = time.time()

    try:
        response = await rpc_client.call_exec(
            destination, "uname", ["-a"], timeout=30.0
        )
        elapsed = time.time() - start

        print_result(response.exit_code == 0, f"Response in {elapsed:.2f}s")
        print(f"\n  Exit code: {response.exit_code}")
        print(f"  Stdout: {response.stdout}")
        if response.stderr:
            print(f"  Stderr: {response.stderr}")

        return response.exit_code == 0

    except Exception as e:
        elapsed = time.time() - start
        print_result(False, f"Exec failed after {elapsed:.2f}s: {e}")
        return False


async def run_full_test() -> None:
    """Run full interactive E2E test suite."""
    print_header("Styrene E2E Manual Test")
    print("  This script walks through multi-node mesh validation.")
    print("  Press Ctrl+C to exit at any time.")

    from styrene.services.app_lifecycle import StyreneLifecycle
    from styrene.services.config import load_config
    from styrene.services.hub_connection import HubStatus, get_hub_connection

    results = {}

    # Initialize once for all tests (RNS is a singleton)
    config = load_config()
    config.reticulum.hub_enabled = True
    lifecycle = StyreneLifecycle(config)

    try:
        # Phase 1.1: Hub connectivity
        print_header("Phase 1.1: Hub Connectivity")

        hub_address = config.reticulum.hub_address
        print(f"  Hub address: {hub_address}")
        print(f"  Hub enabled: {config.reticulum.hub_enabled}")

        print("\n  Initializing Styrene services...")
        if not lifecycle.initialize():
            print_result(False, "Failed to initialize services")
            results["hub_connectivity"] = False
            return
        print_result(True, "Services initialized")

        # Wait for hub connection
        hub_conn = get_hub_connection()
        print(f"\n  Waiting for hub connection (up to 90s)...")
        print(f"  Initial status: {hub_conn.status.value}")

        start = time.time()
        timeout = 90
        last_status = None

        while (time.time() - start) < timeout:
            status = hub_conn.status

            if status != last_status:
                elapsed = time.time() - start
                print(f"  [{elapsed:5.1f}s] Status: {status.value}")
                last_status = status

            if status == HubStatus.CONNECTED:
                print_result(True, f"Connected to hub in {time.time() - start:.1f}s")
                results["hub_connectivity"] = True
                break

            await asyncio.sleep(1)
        else:
            print_result(False, f"Hub connection timeout after {timeout}s")
            results["hub_connectivity"] = False
            print("\n  Hub connectivity failed - cannot proceed with other tests.")
            print("  Check that brutus hub is running and reachable.")
            return

        # Wait a moment for mesh to settle
        await asyncio.sleep(3)

        # Phase 1.2: Node discovery
        discovered = await test_node_discovery(lifecycle)
        results["node_discovery"] = len(discovered) > 0

        # If we discovered nodes, offer RPC test
        if discovered:
            print("\n  Discovered nodes available for RPC test:")
            for i, node in enumerate(discovered):
                print(f"    [{i}] {node['name']} - {node['destination_hash'][:16]}...")

            try:
                choice = input(
                    "\n  Enter node number for RPC test (or Enter to skip): "
                ).strip()
                if choice and choice.isdigit():
                    idx = int(choice)
                    if 0 <= idx < len(discovered):
                        dest = discovered[idx]["destination_hash"]
                        results["rpc_status"] = await test_rpc_roundtrip(
                            dest, lifecycle
                        )
                        results["rpc_exec"] = await test_exec_command(dest, lifecycle)
            except (EOFError, KeyboardInterrupt):
                print("\n  Skipping RPC tests.")

    finally:
        lifecycle.shutdown()

    # Summary
    print_header("Test Summary")
    for test_name, passed in results.items():
        print_result(passed, test_name)

    passed = sum(1 for v in results.values() if v)
    total = len(results)
    print(f"\n  {passed}/{total} tests passed")


async def run_hub_only() -> None:
    """Run only hub connectivity test."""
    await test_hub_connectivity()


async def run_rpc_test(destination: str) -> None:
    """Run RPC test to specific destination."""
    from styrene.services.app_lifecycle import StyreneLifecycle
    from styrene.services.config import load_config

    config = load_config()
    config.reticulum.hub_enabled = True
    lifecycle = StyreneLifecycle(config)

    try:
        print("  Initializing services...")
        lifecycle.initialize()
        await asyncio.sleep(5)  # Wait for mesh announces

        await test_rpc_roundtrip(destination, lifecycle)
        await test_exec_command(destination, lifecycle)

    finally:
        lifecycle.shutdown()


def main() -> None:
    parser = argparse.ArgumentParser(description="E2E manual test script")
    parser.add_argument(
        "--hub-only", action="store_true", help="Only test hub connectivity"
    )
    parser.add_argument(
        "--rpc", metavar="HASH", help="Send RPC to specific destination"
    )
    args = parser.parse_args()

    try:
        if args.hub_only:
            asyncio.run(run_hub_only())
        elif args.rpc:
            asyncio.run(run_rpc_test(args.rpc))
        else:
            asyncio.run(run_full_test())
    except KeyboardInterrupt:
        print("\n\nInterrupted by user.")
        sys.exit(1)


if __name__ == "__main__":
    main()
