"""
Action nodes for API calls, transformations, and custom scripts.
"""

from typing import Any, Dict, Optional
from ybagent.node import BaseNode, NodeType, NodeInput, NodeOutput
import json


class APICallNode(BaseNode):
    """
    Node for making HTTP API calls.
    """
    
    def __init__(
        self,
        id: str,
        url: str = "",
        method: str = "GET",
        headers: Optional[Dict[str, str]] = None,
        **config
    ):
        """
        Initialize API call node.
        
        Args:
            id: Node ID
            url: API endpoint URL
            method: HTTP method (GET, POST, PUT, DELETE, etc.)
            headers: HTTP headers
            **config: Additional configuration
        """
        config.update({
            "url": url,
            "method": method.upper(),
            "headers": headers or {}
        })
        config.pop('node_type', None)
        super().__init__(id=id, node_type=NodeType.API_CALL, **config)
    
    def _define_inputs_outputs(self):
        self.metadata.inputs = [
            NodeInput(name="url", type="str", required=False, description="API URL (overrides config)"),
            NodeInput(name="body", type="any", required=False, description="Request body"),
            NodeInput(name="params", type="dict", required=False, description="Query parameters")
        ]
        self.metadata.outputs = [
            NodeOutput(name="response", type="any", description="API response"),
            NodeOutput(name="status_code", type="int", description="HTTP status code")
        ]
    
    def execute(self, inputs: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
        """
        Make HTTP API call.
        
        Args:
            inputs: URL, body, and parameters
            context: Execution context
        
        Returns:
            API response and status code
        """
        try:
            import httpx
        except ImportError:
            raise ImportError("httpx package not installed. Install with: pip install httpx")
        
        url = inputs.get("url") or self.metadata.config.get("url")
        if not url:
            raise ValueError("URL not provided")
        
        method = self.metadata.config.get("method", "GET")
        headers = self.metadata.config.get("headers", {})
        body = inputs.get("body")
        params = inputs.get("params")
        
        # Make request
        with httpx.Client() as client:
            response = client.request(
                method=method,
                url=url,
                headers=headers,
                json=body if body else None,
                params=params,
                timeout=30.0
            )
        
        # Parse response
        try:
            response_data = response.json()
        except:
            response_data = response.text
        
        return {
            "response": response_data,
            "status_code": response.status_code
        }


class TransformNode(BaseNode):
    """
    Node for data transformation.
    """
    
    def __init__(
        self,
        id: str,
        transform_type: str = "json",
        expression: str = "",
        **config
    ):
        """
        Initialize transform node.
        
        Args:
            id: Node ID
            transform_type: 'json', 'string', 'extract', 'format'
            expression: Transformation expression or key
            **config: Additional configuration
        """
        config.update({
            "transform_type": transform_type,
            "expression": expression
        })
        config.pop('node_type', None)
        super().__init__(id=id, node_type=NodeType.TRANSFORM, **config)
    
    def _define_inputs_outputs(self):
        self.metadata.inputs = [
            NodeInput(name="input", type="any", required=True, description="Input data")
        ]
        self.metadata.outputs = [
            NodeOutput(name="output", type="any", description="Transformed output")
        ]
    
    def execute(self, inputs: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
        """
        Transform input data.
        
        Args:
            inputs: Input data
            context: Execution context
        
        Returns:
            Transformed data
        """
        transform_type = self.metadata.config.get("transform_type", "json")
        expression = self.metadata.config.get("expression", "")
        input_data = inputs.get("input")
        
        if transform_type == "json":
            if isinstance(input_data, str):
                return {"output": json.loads(input_data)}
            else:
                return {"output": json.dumps(input_data)}
        
        elif transform_type == "string":
            return {"output": str(input_data)}
        
        elif transform_type == "extract":
            if not isinstance(input_data, dict):
                raise ValueError("Input must be a dictionary for extraction")
            return {"output": input_data.get(expression)}
        
        elif transform_type == "format":
            if not isinstance(input_data, dict):
                raise ValueError("Input must be a dictionary for formatting")
            try:
                return {"output": expression.format(**input_data)}
            except KeyError as e:
                raise ValueError(f"Missing key for format: {e}")
        
        else:
            raise ValueError(f"Unknown transform type: {transform_type}")


class ScriptNode(BaseNode):
    """
    Node for executing custom Python scripts.
    """
    
    def __init__(self, id: str, script: str = "", **config):
        """
        Initialize script node.
        
        Args:
            id: Node ID
            script: Python script to execute
            **config: Additional configuration
        """
        config["script"] = script
        config.pop('node_type', None)
        super().__init__(id=id, node_type=NodeType.SCRIPT, **config)
    
    def _define_inputs_outputs(self):
        self.metadata.inputs = [
            NodeInput(name="inputs", type="dict", required=True, description="Input variables")
        ]
        self.metadata.outputs = [
            NodeOutput(name="outputs", type="dict", description="Script outputs")
        ]
    
    def execute(self, inputs: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
        """
        Execute Python script.
        
        Args:
            inputs: Input variables
            context: Execution context
        
        Returns:
            Script outputs
        """
        script = self.metadata.config.get("script", "")
        script_inputs = inputs.get("inputs", {})
        
        # Create safe execution environment
        local_scope = script_inputs.copy()
        
        try:
            exec(script, {"__builtins__": {}}, local_scope)
        except Exception as e:
            raise RuntimeError(f"Script execution error: {e}")
        
        # Filter out inputs from outputs
        outputs = {
            k: v for k, v in local_scope.items() 
            if k not in script_inputs and not k.startswith("_")
        }
        
        return {"outputs": outputs}
