"""
BaseNode abstract class for all workflow nodes.
"""

from abc import ABC, abstractmethod
from typing import Dict, Any, List, Optional, Union
from .execution_context import ExecutionContext
from .execution_result import ExecutionResult


class BaseNode(ABC):
    """
    Abstract base class for all workflow nodes.
    
    Defines the common interface and properties that all node types must implement.
    Provides connection management and configuration validation capabilities.
    """
    
    def __init__(self, property_id: str, node_id: str, name: str, config: Union[Dict[str, Any], List[Dict[str, Any]]], database_logger=None):
        """
        Initialize base node.
        
        Args:
            property_id: Property identifier for this node instance
            node_id: Unique identifier for this node instance
            name: Human-readable name for the node
            config: Node-specific configuration dictionary
            database_logger: Optional database logger for node-level logging
        """
        self.property_id = property_id
        self.node_id = node_id
        self.name = name
        self.config = config
        self.database_logger = database_logger
        self.connections: List[str] = []
        self.node_type: Optional[str] = None
        self.position: Dict[str, int] = {"x": 0, "y": 0}
    
    @abstractmethod
    def execute(self, context: ExecutionContext) -> ExecutionResult:
        """
        Execute the node with the given context.
        
        Args:
            context: Current execution context containing workflow state and data
            
        Returns:
            ExecutionResult indicating success/failure and any output data
        """
        pass
    
    @abstractmethod
    def validate_config(self) -> bool:
        """
        Validate the node's configuration.
        
        Returns:
            True if configuration is valid, False otherwise
        """
        pass
    
    def add_connection(self, target_node_id: str) -> None:
        """
        Add a connection to another node.
        
        Args:
            target_node_id: ID of the target node to connect to
        """
        if target_node_id not in self.connections:
            self.connections.append(target_node_id)
    
    def remove_connection(self, target_node_id: str) -> None:
        """
        Remove a connection to another node.
        
        Args:
            target_node_id: ID of the target node to disconnect from
        """
        if target_node_id in self.connections:
            self.connections.remove(target_node_id)
    
    def get_connections(self) -> List[str]:
        """
        Get all outgoing connections from this node.
        
        Returns:
            List of target node IDs
        """
        return self.connections.copy()
    
    def has_connections(self) -> bool:
        """
        Check if this node has any outgoing connections.
        
        Returns:
            True if node has connections, False otherwise
        """
        return len(self.connections) > 0
    
    def set_position(self, x: int, y: int) -> None:
        """
        Set the visual position of the node.
        
        Args:
            x: X coordinate
            y: Y coordinate
        """
        self.position = {"x": x, "y": y}
    
    def get_position(self) -> Dict[str, int]:
        """
        Get the visual position of the node.
        
        Returns:
            Dictionary with x and y coordinates
        """
        return self.position.copy()
    
    def get_config_value(self, key: str, default: Any = None) -> Any:
        if isinstance(self.config, dict):
            return self.config.get(key, default)
        elif isinstance(self.config, list):
            for cfg in self.config:
                if key in cfg:
                    return cfg[key]
            return default
        else:
            return default

    def set_config_value(self, key: str, value: Any) -> None:
        if isinstance(self.config, dict):
            self.config[key] = value
        elif isinstance(self.config, list):
            for cfg in self.config:
                if key in cfg:
                    cfg[key] = value
                    return
            # if not found, add it to the first dict or make new
            if self.config:
                self.config[0][key] = value
            else:
                self.config.append({key: value})
    
    def get_required_config_keys(self) -> List[str]:
        """
        Get list of required configuration keys for this node type.
        
        Returns:
            List of required configuration keys
        """
        return []
    
    def validate_required_config(self) -> bool:
        """
        Validate that all required configuration keys are present.
        
        Returns:
            True if all required keys are present, False otherwise
        """
        required_keys = self.get_required_config_keys()
        for key in required_keys:
            if key not in self.config or self.config[key] is None:
                return False
        return True
    
    def to_dict(self) -> Dict[str, Any]:
        """
        Convert node to dictionary representation.
        
        Returns:
            Dictionary representation of the node
        """
        return {
            'property_id': self.property_id,
            'node_id': self.node_id,
            'name': self.name,
            'type': self.node_type,
            'config': self.config,
            'connections': self.connections,
            'position': self.position
        }
    
    def __str__(self) -> str:
        """String representation of the node."""
        return f"{self.__class__.__name__}(id={self.node_id}, name={self.name})"
    
    def __repr__(self) -> str:
        """Detailed string representation of the node."""
        return f"{self.__class__.__name__}(id={self.node_id}, name={self.name}, connections={len(self.connections)})"