"""
Request Node - Performs HTTP/HTTPS requests with comprehensive options and security features.
"""

import logging
import json
import os
import ssl
import time
import asyncio
import urllib.parse
import re
from typing import Dict, Any, List, Optional, Union, Callable
import aiohttp
import certifi

from .base_node import (
    BaseNode, NodeSchema, NodeParameter, 
    NodeParameterType, NodeValidationError
)

# Configure logging
logger = logging.getLogger(__name__)

class RequestMethodType:
    """HTTP request methods."""
    GET = "GET"
    POST = "POST"
    PUT = "PUT"
    DELETE = "DELETE"
    PATCH = "PATCH"
    HEAD = "HEAD"
    OPTIONS = "OPTIONS"

class AuthType:
    """Authentication types."""
    NONE = "none"
    BASIC = "basic"
    BEARER = "bearer"
    DIGEST = "digest"
    API_KEY = "api_key"
    OAUTH1 = "oauth1"
    OAUTH2 = "oauth2"
    CUSTOM = "custom"

class RequestNode(BaseNode):
    """
    Node for making HTTP/HTTPS requests.
    Provides functionality for all common HTTP methods with comprehensive options.
    """
    
    def __init__(self, sandbox_timeout: Optional[int] = None):
        super().__init__(sandbox_timeout=sandbox_timeout)
        # Create session on initialization for reuse
        self.session = None
        self.ssl_context = self._create_ssl_context()
        # Storage for resources
        self.resources = {}
        self.cache_store = {}  # Simple in-memory cache
    
    def _create_ssl_context(self):
        """
        Creates an SSL context that uses certifi's CA bundle.
        This ensures requests use valid CA certificates.
        """
        context = ssl.create_default_context()
        context.load_verify_locations(certifi.where())
        return context
    
    def get_schema(self) -> NodeSchema:
        """Return the schema definition for the request node."""
        return NodeSchema(
            node_type="request",
            version="1.0.0",
            description="Makes HTTP/HTTPS requests with comprehensive options",
            parameters=[
                # Basic request parameters
                NodeParameter(
                    name="url",
                    type=NodeParameterType.STRING,
                    description="URL to send the request to",
                    required=True
                ),
                NodeParameter(
                    name="method",
                    type=NodeParameterType.STRING,
                    description="HTTP method to use",
                    required=True,
                    default=RequestMethodType.GET,
                    enum=[
                        RequestMethodType.GET, RequestMethodType.POST, RequestMethodType.PUT,
                        RequestMethodType.DELETE, RequestMethodType.PATCH, RequestMethodType.HEAD,
                        RequestMethodType.OPTIONS
                    ]
                ),
                
                # Request data parameters
                NodeParameter(
                    name="headers",
                    type=NodeParameterType.OBJECT,
                    description="HTTP headers to include in the request",
                    required=False,
                    default={}
                ),
                NodeParameter(
                    name="query_params",
                    type=NodeParameterType.OBJECT,
                    description="Query parameters to append to the URL",
                    required=False,
                    default={}
                ),
                NodeParameter(
                    name="body",
                    type=NodeParameterType.ANY,
                    description="Request body (for POST, PUT, etc.)",
                    required=False
                ),
                NodeParameter(
                    name="body_type",
                    type=NodeParameterType.STRING,
                    description="Content type of the request body",
                    required=False,
                    default="json",
                    enum=["json", "form", "text", "raw", "binary", "multipart"]
                ),
                NodeParameter(
                    name="files",
                    type=NodeParameterType.OBJECT,
                    description="Files to upload (for multipart requests)",
                    required=False
                ),
                
                # Authentication parameters
                NodeParameter(
                    name="auth_type",
                    type=NodeParameterType.STRING,
                    description="Authentication type to use",
                    required=False,
                    default=AuthType.NONE,
                    enum=[
                        AuthType.NONE, AuthType.BASIC, AuthType.BEARER, AuthType.DIGEST,
                        AuthType.API_KEY, AuthType.OAUTH1, AuthType.OAUTH2, AuthType.CUSTOM
                    ]
                ),
                NodeParameter(
                    name="username",
                    type=NodeParameterType.STRING,
                    description="Username for basic authentication",
                    required=False
                ),
                NodeParameter(
                    name="password",
                    type=NodeParameterType.STRING,  # Changed from SECRET
                    description="Password for basic authentication",
                    required=False
                ),
                NodeParameter(
                    name="token",
                    type=NodeParameterType.STRING,  # Changed from SECRET
                    description="Token for bearer authentication",
                    required=False
                ),
                NodeParameter(
                    name="api_key",
                    type=NodeParameterType.STRING,  # Changed from SECRET
                    description="API key for API key authentication",
                    required=False
                ),
                NodeParameter(
                    name="api_key_name",
                    type=NodeParameterType.STRING,
                    description="Name of the API key parameter",
                    required=False,
                    default="api_key"
                ),
                NodeParameter(
                    name="api_key_location",
                    type=NodeParameterType.STRING,
                    description="Location to place the API key",
                    required=False,
                    default="query",
                    enum=["query", "header", "cookie"]
                ),
                
                # Connection parameters
                NodeParameter(
                    name="timeout",
                    type=NodeParameterType.NUMBER,
                    description="Request timeout in seconds",
                    required=False,
                    default=30
                ),
                NodeParameter(
                    name="verify_ssl",
                    type=NodeParameterType.BOOLEAN,
                    description="Whether to verify SSL certificates",
                    required=False,
                    default=True
                ),
                NodeParameter(
                    name="follow_redirects",
                    type=NodeParameterType.BOOLEAN,
                    description="Whether to follow redirects",
                    required=False,
                    default=True
                ),
                NodeParameter(
                    name="max_redirects",
                    type=NodeParameterType.NUMBER,
                    description="Maximum number of redirects to follow",
                    required=False,
                    default=10
                ),
                NodeParameter(
                    name="retry_count",
                    type=NodeParameterType.NUMBER,
                    description="Number of times to retry the request on failure",
                    required=False,
                    default=0
                ),
                NodeParameter(
                    name="retry_delay",
                    type=NodeParameterType.NUMBER,
                    description="Delay between retries in seconds",
                    required=False,
                    default=1
                ),
                
                # Response parameters
                NodeParameter(
                    name="response_type",
                    type=NodeParameterType.STRING,
                    description="How to parse the response",
                    required=False,
                    default="auto",
                    enum=["auto", "json", "text", "binary", "arraybuffer"]
                ),
                NodeParameter(
                    name="fail_on_error",
                    type=NodeParameterType.BOOLEAN,
                    description="Whether to fail if the response has an error status code",
                    required=False,
                    default=True
                ),
                NodeParameter(
                    name="extract_path",
                    type=NodeParameterType.STRING,
                    description="JSON path to extract from the response (dot notation)",
                    required=False
                ),
                
                # Caching parameters
                NodeParameter(
                    name="use_cache",
                    type=NodeParameterType.BOOLEAN,
                    description="Whether to use caching",
                    required=False,
                    default=False
                ),
                NodeParameter(
                    name="cache_ttl",
                    type=NodeParameterType.NUMBER,
                    description="Cache time-to-live in seconds",
                    required=False,
                    default=300
                ),
                
                # Advanced parameters
                NodeParameter(
                    name="proxy",
                    type=NodeParameterType.STRING,
                    description="Proxy URL to use for the request",
                    required=False
                ),
                NodeParameter(
                    name="cookies",
                    type=NodeParameterType.OBJECT,
                    description="Cookies to include in the request",
                    required=False,
                    default={}
                ),
                NodeParameter(
                    name="user_agent",
                    type=NodeParameterType.STRING,
                    description="User agent string to use",
                    required=False
                ),
                NodeParameter(
                    name="compression",
                    type=NodeParameterType.BOOLEAN,
                    description="Whether to enable compression",
                    required=False,
                    default=True
                )
            ],
            
            # Define outputs for the node
            outputs={
                "status": NodeParameterType.STRING,
                "status_code": NodeParameterType.NUMBER,
                "headers": NodeParameterType.OBJECT,
                "body": NodeParameterType.ANY,
                "error": NodeParameterType.STRING,
                "response_time": NodeParameterType.NUMBER,
                "redirect_chain": NodeParameterType.ARRAY,
                "cached": NodeParameterType.BOOLEAN
            },
            
            # Add metadata
            tags=["http", "api", "networking", "integration"],
            author="System"
        )
    
    def validate_custom(self, node_data: Dict[str, Any]) -> Dict[str, Any]:
        """Custom validation for request parameters."""
        params = node_data.get("params", {})
        
        # Validate URL
        url = params.get("url")
        if not url:
            raise NodeValidationError("URL is required")
            
        # Validate that URL is well-formed
        try:
            parsed_url = urllib.parse.urlparse(url)
            if not parsed_url.scheme or not parsed_url.netloc:
                raise NodeValidationError("Invalid URL format")
        except Exception:
            raise NodeValidationError("Invalid URL format")
            
        # Validate authentication parameters
        auth_type = params.get("auth_type", AuthType.NONE)
        if auth_type == AuthType.BASIC:
            if not params.get("username") or not params.get("password"):
                raise NodeValidationError("Username and password are required for basic authentication")
        elif auth_type == AuthType.BEARER:
            if not params.get("token"):
                raise NodeValidationError("Token is required for bearer authentication")
        elif auth_type == AuthType.API_KEY:
            if not params.get("api_key"):
                raise NodeValidationError("API key is required for API key authentication")
        
        # Validate file upload parameters
        body_type = params.get("body_type", "json")
        if body_type == "multipart" and not params.get("files"):
            raise NodeValidationError("Files are required for multipart requests")
        
        # Validate timeout value
        timeout = params.get("timeout", 30)
        if timeout <= 0:
            raise NodeValidationError("Timeout must be a positive number")
        
        # Validate retry parameters
        retry_count = params.get("retry_count", 0)
        if retry_count < 0:
            raise NodeValidationError("Retry count cannot be negative")
            
        retry_delay = params.get("retry_delay", 1)
        if retry_delay <= 0:
            raise NodeValidationError("Retry delay must be a positive number")
        
        return {}
    
    async def execute(self, node_data: Dict[str, Any]) -> Dict[str, Any]:
        """Execute the request node."""
        try:
            # Validate schema and parameters
            validated_data = self.validate_schema(node_data)
            
            # Get method type
            method = validated_data.get("method", RequestMethodType.GET)
            
            # Execute the appropriate operation based on the method
            if method == RequestMethodType.GET:
                return await self.operation_get_request(validated_data)
            elif method == RequestMethodType.POST:
                return await self.operation_post_request(validated_data)
            elif method == RequestMethodType.PUT:
                return await self.operation_put_request(validated_data)
            elif method == RequestMethodType.DELETE:
                return await self.operation_delete_request(validated_data)
            else:
                # For other methods, use the generic send_request
                return await self.operation_send_request(validated_data)
                
        except Exception as e:
            error_message = f"Error in request node: {str(e)}"
            logger.error(error_message)
            return {
                "status": "error",
                "status_code": None,
                "headers": {},
                "body": None,
                "error": error_message,
                "response_time": 0,
                "redirect_chain": [],
                "cached": False
            }
    
    # -------------------------
    # Operation Methods
    # -------------------------
    
    async def operation_send_request(self, params: Dict[str, Any]) -> Dict[str, Any]:
        """
        Send an HTTP request with the specified parameters.
        
        Args:
            params: Request parameters
            
        Returns:
            Response information
        """
        # Extract parameters
        url = params["url"]
        method = params.get("method", RequestMethodType.GET)
        headers = params.get("headers", {})
        query_params = params.get("query_params", {})
        body = params.get("body")
        body_type = params.get("body_type", "json")
        timeout = params.get("timeout", 30)
        verify_ssl = params.get("verify_ssl", True)
        follow_redirects = params.get("follow_redirects", True)
        max_redirects = params.get("max_redirects", 10)
        retry_count = params.get("retry_count", 0)
        retry_delay = params.get("retry_delay", 1)
        response_type = params.get("response_type", "auto")
        fail_on_error = params.get("fail_on_error", True)
        use_cache = params.get("use_cache", False)
        cache_ttl = params.get("cache_ttl", 300)
        proxy = params.get("proxy")
        cookies = params.get("cookies", {})
        user_agent = params.get("user_agent")
        
        # Add User-Agent header if specified
        if user_agent:
            headers["User-Agent"] = user_agent
        
        # Add authentication
        headers = self._add_authentication(headers, params)
        
        # Generate a cache key if caching is enabled
        cache_key = None
        cached_response = None
        if use_cache:
            cache_key = self._generate_cache_key(url, method, headers, query_params, body)
            cached_response = self._get_from_cache(cache_key)
            if cached_response:
                cached_response["cached"] = True
                return cached_response
        
        # Start timing the request
        start_time = time.time()
        redirect_chain = []
        session = None
        
        try:
            # Create or reuse a session
            session = self.session or aiohttp.ClientSession()
            if not self.session:
                self.session = session
            
            # Process query parameters
            if query_params:
                # Parse the URL
                parsed_url = urllib.parse.urlparse(url)
                # Parse existing query parameters
                existing_params = urllib.parse.parse_qs(parsed_url.query)
                # Merge with new query parameters
                for key, value in query_params.items():
                    existing_params[key] = [value]
                # Build new query string
                new_query = urllib.parse.urlencode(existing_params, doseq=True)
                # Reconstruct the URL
                url = urllib.parse.urlunparse((
                    parsed_url.scheme,
                    parsed_url.netloc,
                    parsed_url.path,
                    parsed_url.params,
                    new_query,
                    parsed_url.fragment
                ))
            
            # Prepare request parameters
            request_kwargs = {
                "url": url,
                "headers": headers,
                "cookies": cookies,
                "timeout": aiohttp.ClientTimeout(total=timeout),
                "ssl": None if not verify_ssl else self.ssl_context,
                "allow_redirects": follow_redirects,
                "max_redirects": max_redirects,
            }
            
            # Add proxy if specified
            if proxy:
                request_kwargs["proxy"] = proxy
            
            # Add body based on body_type
            if body is not None and method != RequestMethodType.GET and method != RequestMethodType.HEAD:
                if body_type == "json":
                    request_kwargs["json"] = body
                    if "Content-Type" not in headers:
                        headers["Content-Type"] = "application/json"
                elif body_type == "form":
                    request_kwargs["data"] = body
                    if "Content-Type" not in headers:
                        headers["Content-Type"] = "application/x-www-form-urlencoded"
                elif body_type == "text":
                    request_kwargs["data"] = body
                    if "Content-Type" not in headers:
                        headers["Content-Type"] = "text/plain"
                elif body_type == "raw":
                    request_kwargs["data"] = body
                elif body_type == "binary":
                    request_kwargs["data"] = body
                    if "Content-Type" not in headers:
                        headers["Content-Type"] = "application/octet-stream"
                elif body_type == "multipart":
                    # Handle multipart form data
                    form_data = aiohttp.FormData()
                    for key, value in body.items():
                        form_data.add_field(key, value)
                    
                    # Add files if specified
                    files = params.get("files", {})
                    for file_field, file_info in files.items():
                        form_data.add_field(
                            file_field,
                            open(file_info["path"], "rb"),
                            filename=file_info.get("filename", os.path.basename(file_info["path"])),
                            content_type=file_info.get("content_type", "application/octet-stream")
                        )
                    
                    request_kwargs["data"] = form_data
            
            # Execute the request with retries
            attempts = 0
            max_attempts = retry_count + 1
            response = None
            last_error = None
            
            while attempts < max_attempts:
                try:
                    method_func = getattr(session, method.lower())
                    async with method_func(**request_kwargs) as response:
                        # Collect redirect information
                        if follow_redirects and hasattr(response, 'history'):
                            redirect_chain = [
                                {"url": r.url.human_repr(), "status_code": r.status}
                                for r in response.history
                            ]
                        
                        # Check for error status
                        if fail_on_error and response.status >= 400:
                            last_error = f"HTTP error: {response.status} {response.reason}"
                            raise Exception(last_error)
                        
                        # Process response based on content type
                        response_body = await self._process_response(response, response_type)
                        
                        # Extract data if path specified
                        extract_path = params.get("extract_path")
                        if extract_path and isinstance(response_body, dict):
                            response_body = self._extract_from_path(response_body, extract_path)
                        
                        # Calculate response time
                        response_time = time.time() - start_time
                        
                        # Create result
                        result = {
                            "status": "success",
                            "status_code": response.status,
                            "headers": dict(response.headers),
                            "body": response_body,
                            "error": None,
                            "response_time": response_time,
                            "redirect_chain": redirect_chain,
                            "cached": False
                        }
                        
                        # Cache the result if needed
                        if use_cache and cache_key:
                            self._store_in_cache(cache_key, result, cache_ttl)
                        
                        return result
                
                except Exception as e:
                    last_error = str(e)
                    attempts += 1
                    if attempts < max_attempts:
                        await asyncio.sleep(retry_delay)
            
            # If we get here, all retry attempts failed
            error_message = f"Request failed after {max_attempts} attempts: {last_error}"
            logger.error(error_message)
            
            return {
                "status": "error",
                "status_code": response.status if response else None,
                "headers": dict(response.headers) if response else {},
                "body": None,
                "error": error_message,
                "response_time": time.time() - start_time,
                "redirect_chain": redirect_chain,
                "cached": False
            }
            
        except Exception as e:
            error_message = f"Request error: {str(e)}"
            logger.error(error_message)
            
            return {
                "status": "error",
                "status_code": None,
                "headers": {},
                "body": None,
                "error": error_message,
                "response_time": time.time() - start_time,
                "redirect_chain": [],
                "cached": False
            }
        finally:
            # Only close the session if we created it in this method
            if session and not self.session:
                await session.close()
    
    async def operation_get_request(self, params: Dict[str, Any]) -> Dict[str, Any]:
        """
        Send a GET request.
        
        Args:
            params: Request parameters
            
        Returns:
            Response information
        """
        # Ensure the method is GET
        params["method"] = RequestMethodType.GET
        return await self.operation_send_request(params)
    
    async def operation_post_request(self, params: Dict[str, Any]) -> Dict[str, Any]:
        """
        Send a POST request.
        
        Args:
            params: Request parameters
            
        Returns:
            Response information
        """
        # Ensure the method is POST
        params["method"] = RequestMethodType.POST
        return await self.operation_send_request(params)
    
    async def operation_put_request(self, params: Dict[str, Any]) -> Dict[str, Any]:
        """
        Send a PUT request.
        
        Args:
            params: Request parameters
            
        Returns:
            Response information
        """
        # Ensure the method is PUT
        params["method"] = RequestMethodType.PUT
        return await self.operation_send_request(params)
    
    async def operation_delete_request(self, params: Dict[str, Any]) -> Dict[str, Any]:
        """
        Send a DELETE request.
        
        Args:
            params: Request parameters
            
        Returns:
            Response information
        """
        # Ensure the method is DELETE
        params["method"] = RequestMethodType.DELETE
        return await self.operation_send_request(params)
    
    async def operation_parse_response(self, params: Dict[str, Any]) -> Dict[str, Any]:
        """
        Parse a response according to its content type.
        
        Args:
            params: Parameters including the response
            
        Returns:
            Parsed data
        """
        response = params.get("response")
        if not response:
            return {"parsed_data": None, "error": "No response provided"}
        
        try:
            # Try to determine the content type
            content_type = response.get("headers", {}).get("Content-Type", "")
            
            # Parse based on content type
            body = response.get("body")
            
            if "application/json" in content_type:
                if isinstance(body, str):
                    parsed_data = json.loads(body)
                else:
                    parsed_data = body
            elif "text/" in content_type:
                # Text content
                parsed_data = body
            elif "application/xml" in content_type or "text/xml" in content_type:
                # XML content - return as string for further processing
                parsed_data = body
            else:
                # Default fallback - return as is
                parsed_data = body
            
            return {"parsed_data": parsed_data, "error": None}
            
        except Exception as e:
            error_message = f"Error parsing response: {str(e)}"
            logger.error(error_message)
            return {"parsed_data": None, "error": error_message}
    
    # -------------------------
    # Helper Methods
    # -------------------------
    
    def _add_authentication(self, headers: Dict[str, str], params: Dict[str, Any]) -> Dict[str, str]:
        """
        Add authentication headers based on the specified auth type.
        
        Args:
            headers: Existing headers
            params: Request parameters
            
        Returns:
            Updated headers with authentication
        """
        auth_type = params.get("auth_type", AuthType.NONE)
        
        if auth_type == AuthType.NONE:
            return headers
            
        # Create a copy of headers to avoid modifying the original
        headers = headers.copy()
        
        if auth_type == AuthType.BASIC:
            username = params.get("username", "")
            password = params.get("password", "")
            
            import base64
            auth_string = f"{username}:{password}"
            auth_bytes = auth_string.encode("utf-8")
            encoded = base64.b64encode(auth_bytes).decode("utf-8")
            
            headers["Authorization"] = f"Basic {encoded}"
            
        elif auth_type == AuthType.BEARER:
            token = params.get("token", "")
            headers["Authorization"] = f"Bearer {token}"
            
        elif auth_type == AuthType.API_KEY:
            api_key = params.get("api_key", "")
            api_key_name = params.get("api_key_name", "api_key")
            api_key_location = params.get("api_key_location", "query")
            
            if api_key_location == "header":
                headers[api_key_name] = api_key
            # For query and cookie, handled elsewhere
            
        # OAuth and other auth types would be handled here
        
        return headers
    
    async def _process_response(self, response, response_type: str) -> Any:
        """
        Process the response based on the specified type.
        
        Args:
            response: The HTTP response
            response_type: How to process the response
            
        Returns:
            Processed response body
        """
        if response_type == "auto":
            # Try to determine the type from Content-Type header
            content_type = response.headers.get("Content-Type", "").lower()
            
            if "application/json" in content_type:
                return await response.json()
            elif "text/" in content_type:
                return await response.text()
            elif "application/octet-stream" in content_type or "image/" in content_type:
                return await response.read()
            else:
                # Default to text
                return await response.text()
                
        elif response_type == "json":
            try:
                return await response.json()
            except Exception:
                # If JSON parsing fails, return as text
                return await response.text()
                
        elif response_type == "text":
            return await response.text()
            
        elif response_type == "binary" or response_type == "arraybuffer":
            return await response.read()
            
        else:
            # Default fallback
            return await response.text()
    
    def _generate_cache_key(self, url: str, method: str, headers: Dict[str, str], 
                            query_params: Dict[str, Any], body: Any) -> str:
        """
        Generate a cache key for the request.
        
        Args:
            url: Request URL
            method: HTTP method
            headers: Request headers
            query_params: Query parameters
            body: Request body
            
        Returns:
            Cache key string
        """
        import hashlib
        
        # Create a string representation of the request
        key_parts = [
            method.upper(),
            url,
            json.dumps(headers, sort_keys=True),
            json.dumps(query_params, sort_keys=True) if query_params else "",
            json.dumps(body, sort_keys=True) if body and isinstance(body, (dict, list)) else str(body) if body else ""
        ]
        
        key_string = "|".join(key_parts)
        
        # Create a hash of the string
        return hashlib.md5(key_string.encode("utf-8")).hexdigest()
    
    def _get_from_cache(self, cache_key: str) -> Optional[Dict[str, Any]]:
        """
        Get a response from the cache.
        
        Args:
            cache_key: Cache key to look up
            
        Returns:
            Cached response or None if not found
        """
        # Use our simple cache store
        if cache_key not in self.cache_store:
            return None
            
        # Try to get the response from the cache
        cached_item = self.cache_store.get(cache_key)
        if not cached_item:
            return None
            
        # Check if the cached item has expired
        timestamp, response = cached_item
        current_time = time.time()
        
        if current_time - timestamp > response.get("cache_ttl", 300):
            # Item has expired
            return None
            
        return response
    
    def _store_in_cache(self, cache_key: str, response: Dict[str, Any], cache_ttl: int) -> None:
        """
        Store a response in the cache.
        
        Args:
            cache_key: Cache key to store under
            response: Response to cache
            cache_ttl: Cache time-to-live in seconds
            
        Returns:
            None
        """
        # Add cache TTL to the response for expiration check
        response_copy = response.copy()
        response_copy["cache_ttl"] = cache_ttl
        
        # Store the response with a timestamp in our simple cache
        self.cache_store[cache_key] = (time.time(), response_copy)
    
    def _extract_from_path(self, data: Dict[str, Any], path: str) -> Any:
        """
        Extract a value from a nested dictionary using dot notation.
        
        Args:
            data: Dictionary to extract from
            path: Path in dot notation (e.g., "data.items.0.name")
            
        Returns:
            Extracted value or None if not found
        """
        if not path:
            return data
            
        current = data
        parts = path.split(".")
        
        for part in parts:
            # Handle array indices
            if part.isdigit() and isinstance(current, list):
                index = int(part)
                if 0 <= index < len(current):
                    current = current[index]
                else:
                    return None
            # Handle dictionary keys
            elif isinstance(current, dict) and part in current:
                current = current[part]
            else:
                return None
                
        return current
    
    async def close(self):
        """Close resources used by this node."""
        if self.session:
            await self.session.close()
            self.session = None

# Register with NodeRegistry
try:
    from node_registry import NodeRegistry
    # Create registry instance and register the node
    registry = NodeRegistry()
    registry.register("request", RequestNode)
    logger.info("Successfully registered RequestNode with registry")
except ImportError:
    logger.warning("Could not register RequestNode with registry - module not found")
except Exception as e:
    logger.error(f"Error registering RequestNode with registry: {str(e)}")

# This goes at the end of a modified RequestNode.py that only imports available components from base_node

# Main test suite for RequestNode
if __name__ == "__main__":
    # Configure logging for testing
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    )
    
    # Create async test runner
    async def run_tests():
        print("=== RequestNode Test Suite ===")
        
        # Create an instance of the RequestNode
        node = RequestNode()
        
        # Test different HTTP methods against httpbin.org (a test service)
        test_cases = [
            {
                "name": "GET Request",
                "params": {
                    "url": "https://httpbin.org/get",
                    "method": RequestMethodType.GET,
                    "query_params": {"test": "value", "foo": "bar"}
                },
                "expected_status": "success"
            },
            {
                "name": "POST Request - JSON Body",
                "params": {
                    "url": "https://httpbin.org/post",
                    "method": RequestMethodType.POST,
                    "body": {"name": "Test User", "email": "test@example.com"},
                    "body_type": "json"
                },
                "expected_status": "success"
            },
            {
                "name": "PUT Request",
                "params": {
                    "url": "https://httpbin.org/put",
                    "method": RequestMethodType.PUT,
                    "body": {"id": 123, "name": "Updated User"},
                    "body_type": "json"
                },
                "expected_status": "success"
            },
            {
                "name": "DELETE Request",
                "params": {
                    "url": "https://httpbin.org/delete",
                    "method": RequestMethodType.DELETE
                },
                "expected_status": "success"
            },
            {
                "name": "Form Data POST",
                "params": {
                    "url": "https://httpbin.org/post",
                    "method": RequestMethodType.POST,
                    "body": {"field1": "value1", "field2": "value2"},
                    "body_type": "form"
                },
                "expected_status": "success"
            },
            {
                "name": "Headers Test",
                "params": {
                    "url": "https://httpbin.org/headers",
                    "method": RequestMethodType.GET,
                    "headers": {"X-Custom-Header": "TestValue", "X-Another-Header": "AnotherValue"}
                },
                "expected_status": "success",
                "validation": lambda result: "X-Custom-Header" in str(result["body"])
            },
            {
                "name": "Basic Auth",
                "params": {
                    "url": "https://httpbin.org/basic-auth/user/pass",
                    "method": RequestMethodType.GET,
                    "auth_type": AuthType.BASIC,
                    "username": "user",
                    "password": "pass"
                },
                "expected_status": "success",
                "validation": lambda result: result["body"].get("authenticated") == True
            },
            {
                "name": "Bearer Auth",
                "params": {
                    "url": "https://httpbin.org/bearer",
                    "method": RequestMethodType.GET,
                    "auth_type": AuthType.BEARER,
                    "token": "test-token-12345"
                },
                "expected_status": "success",
                "validation": lambda result: result["body"].get("authenticated") == True
            },
            {
                "name": "Response Type - JSON",
                "params": {
                    "url": "https://httpbin.org/json",
                    "method": RequestMethodType.GET,
                    "response_type": "json"
                },
                "expected_status": "success",
                "validation": lambda result: "slideshow" in result["body"]
            },
       {
    "name": "Extract Path",
    "params": {
        "url": "https://httpbin.org/json",
        "method": RequestMethodType.GET,
        "extract_path": "slideshow.title"  # Changed from "slideshow.slides.0.title"
    },
    "expected_status": "success",
    "validation": lambda result: isinstance(result["body"], str) and "wonder" in result["body"].lower()
},
            {
                "name": "User Agent",
                "params": {
                    "url": "https://httpbin.org/user-agent",
                    "method": RequestMethodType.GET,
                    "user_agent": "CustomTestAgent/1.0"
                },
                "expected_status": "success",
                "validation": lambda result: "CustomTestAgent" in str(result["body"])
            },
            {
                "name": "Cookies",
                "params": {
                    "url": "https://httpbin.org/cookies",
                    "method": RequestMethodType.GET,
                    "cookies": {"test_cookie": "cookie_value", "another_cookie": "another_value"}
                },
                "expected_status": "success",
                "validation": lambda result: "test_cookie" in str(result["body"])
            },
       {
    "name": "Timeout Test",
    "params": {
        "url": "https://httpbin.org/delay/1",
        "method": RequestMethodType.GET,
        "timeout": 5  # Increased from 2 to 5 seconds
    },
    "expected_status": "success"
},
            {
                "name": "Redirect Test",
                "params": {
                    "url": "https://httpbin.org/redirect/1",
                    "method": RequestMethodType.GET,
                    "follow_redirects": True
                },
                "expected_status": "success",
                "validation": lambda result: len(result["redirect_chain"]) > 0
            },
            {
                "name": "Error Status Code",
                "params": {
                    "url": "https://httpbin.org/status/404",
                    "method": RequestMethodType.GET,
                    "fail_on_error": False  # Don't fail on 404
                },
                "expected_status": "success",
                "validation": lambda result: result["status_code"] == 404
            }
        ]
        
        # Run all test cases
        total_tests = len(test_cases)
        passed_tests = 0
        
        for test_case in test_cases:
            print(f"\nRunning test: {test_case['name']}")
            
            try:
                # Prepare node data
                node_data = {
                    "params": test_case["params"]
                }
                
                # Execute the node
                result = await node.execute(node_data)
                
                # Check if the result status matches expected status
                if result["status"] == test_case["expected_status"]:
                    # Run additional validation if provided
                    if "validation" in test_case:
                        validation_func = test_case["validation"]
                        if validation_func(result):
                            print(f"✅ PASS: {test_case['name']} - Status: {result['status']} - Validation passed")
                            passed_tests += 1
                        else:
                            print(f"❌ FAIL: {test_case['name']} - Validation failed")
                            print(f"Response: {result}")
                    else:
                        print(f"✅ PASS: {test_case['name']} - Status: {result['status']}")
                        passed_tests += 1
                else:
                    print(f"❌ FAIL: {test_case['name']} - Expected status {test_case['expected_status']}, got {result['status']}")
                    print(f"Error: {result.get('error')}")
                    
            except Exception as e:
                print(f"❌ FAIL: {test_case['name']} - Exception: {str(e)}")
        
        # Print summary
        print(f"\n=== Test Summary ===")
        print(f"Total tests: {total_tests}")
        print(f"Passed: {passed_tests}")
        print(f"Failed: {total_tests - passed_tests}")
        print(f"Success rate: {passed_tests / total_tests * 100:.1f}%")
        
        # Additional manual tests for specific scenarios
        print("\n=== Manual Test: Error Handling ===")
        # Test with invalid URL
        try:
            invalid_url_result = await node.execute({
                "params": {
                    "url": "https://nonexistenturlxyz123456.com",
                    "method": RequestMethodType.GET,
                    "timeout": 5,
                    "retry_count": 1
                }
            })
            print(f"Response for invalid URL: Status={invalid_url_result['status']}, Error={invalid_url_result['error']}")
        except Exception as e:
            print(f"Caught exception with invalid URL: {str(e)}")
        
        # Test with operation timeout
        try:
            timeout_result = await node.execute({
                "params": {
                    "url": "https://httpbin.org/delay/3",
                    "method": RequestMethodType.GET,
                    "timeout": 1  # 1 second timeout for a 3 second delay should fail
                }
            })
            print(f"Response for timeout test: Status={timeout_result['status']}, Error={timeout_result['error']}")
        except Exception as e:
            print(f"Caught exception with timeout: {str(e)}")
        
        print("\n=== Manual Test: Retry Functionality ===")
        # Test retry functionality with a flaky endpoint
        retry_result = await node.execute({
            "params": {
                "url": "https://httpbin.org/status/500",
                "method": RequestMethodType.GET,
                "retry_count": 2,
                "retry_delay": 1,
                "fail_on_error": False
            }
        })
        print(f"Response after retries: Status={retry_result['status']}, Status Code={retry_result['status_code']}")
        
        print("\n=== Manual Test: Response Parsing ===")
        # Test different response types
        response_types = ["auto", "json", "text", "binary"]
        for resp_type in response_types:
            response_result = await node.execute({
                "params": {
                    "url": "https://httpbin.org/json",
                    "method": RequestMethodType.GET,
                    "response_type": resp_type
                }
            })
            print(f"Response with {resp_type} parsing: Type={type(response_result['body']).__name__}")
        
        print("\n=== Performance Test: Multiple Concurrent Requests ===")
        # Perform multiple concurrent requests to test performance
        import time
        import asyncio
        
        # Define a list of URLs to request
        urls = [
            "https://httpbin.org/get?param=1",
            "https://httpbin.org/get?param=2",
            "https://httpbin.org/get?param=3",
            "https://httpbin.org/get?param=4",
            "https://httpbin.org/get?param=5"
        ]
        
        # Create tasks for all requests
        tasks = []
        start_time = time.time()
        
        for url in urls:
            task = node.execute({
                "params": {
                    "url": url,
                    "method": RequestMethodType.GET
                }
            })
            tasks.append(task)
        
        # Run all requests concurrently
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        end_time = time.time()
        execution_time = end_time - start_time
        
        successful = sum(1 for r in results if isinstance(r, dict) and r["status"] == "success")
        print(f"Completed {len(urls)} concurrent requests in {execution_time:.4f} seconds")
        print(f"Successful requests: {successful}/{len(urls)}")
        
        # Try to close the session
        await node.close()
        print("\nAll tests completed!")

    # Run the async tests
    asyncio.run(run_tests())