from typing import Any
from loguru import logger
from sqlalchemy import select, inspect
from sqlalchemy.orm import InstrumentedAttribute
from service_forge.workflow.node import Node
from service_forge.workflow.port import Port

class DatabaseUpdateFieldsNode(Node):
    DEFAULT_INPUT_PORTS = [
        Port("model", Any),
        Port("conditions", str, is_extended=True),
        Port("fields", tuple[str, Any], is_extended=True),
    ]

    DEFAULT_OUTPUT_PORTS = [
        Port("data", Any),
    ]

    def __init__(self, name: str):
        super().__init__(name)

    async def _run(
        self, 
        model: Any,
        conditions: list[tuple[int, str]] = None,
        fields: list[tuple[int, tuple[str, Any]]] = None,
    ) -> None:
        if hasattr(model, '__table__') or hasattr(model, '__mapper__'):
            if isinstance(model, type):
                model_class = model
            else:
                model_class = type(model)
        else:
            raise ValueError(f"model must be a SQLAlchemy model instance or class, got {model}")
        
        condition_list = []
        if conditions:
            for _, cond_str in conditions:
                condition_list.append(cond_str)
        
        update_fields = {}
        if fields:
            # for _, field in fields:
            #     update_fields[field[0]] = field[1]
            for _, field in fields:
                if type(field) == str:
                    field = eval(field)
                update_fields[field[0]] = field[1]
        
        if not update_fields:
            raise ValueError("No fields provided for update")
        
        sqlalchemy_conditions = self._build_sqlalchemy_conditions(model_class, condition_list)
        
        session_factory = await self.default_postgres_database.get_session_factory()
        async with session_factory() as session:
            try:
                query = select(model_class)
                if sqlalchemy_conditions:
                    query = query.where(*sqlalchemy_conditions)
                
                result = await session.execute(query)
                matching_records = result.scalars().all()
                
                if not matching_records:
                    logger.warning(f"No records found matching conditions in {model_class.__tablename__}")
                    self.activate_output_edges(self.get_output_port_by_name('data'), [])
                    return
                
                for record in matching_records:
                    for field_name, field_value in update_fields.items():
                        if not hasattr(model_class, field_name):
                            logger.warning(f"Field '{field_name}' does not exist in model {model_class.__name__}, skipping")
                            continue
                        setattr(record, field_name, field_value)
                
                await session.commit()
                
                for record in matching_records:
                    await session.refresh(record)
                
                logger.info(f"✓ Updated {len(matching_records)} record(s) in {model_class.__tablename__}")
                
                self.activate_output_edges(self.get_output_port_by_name('data'), matching_records)
                    
            except Exception as e:
                logger.error(f"✗ Failed to update records: {e}")
                await session.rollback()
                raise
    
    def _build_sqlalchemy_conditions(self, model_class: type, condition_strings: list[str]) -> list:
        """Build SQLAlchemy conditions from condition strings
        
        Supports formats:
        - "Model.field == value" (full path)
        - "field == value" (simple equality)
        - "field != value" (inequality)
        - "field > value", "field < value", etc. (comparisons)
        """
        conditions = []
        
        for cond_str in condition_strings:
            if not cond_str or not isinstance(cond_str, str):
                continue
                
            try:
                cond_str = cond_str.strip()
                
                # Try full path format first: "Model.field == value"
                if '.' in cond_str and model_class.__name__ in cond_str:
                    try:
                        safe_dict = {
                            model_class.__name__: model_class,
                            '__builtins__': {},
                            'True': True,
                            'False': False,
                            'None': None,
                        }
                        condition = eval(cond_str, safe_dict)
                        if condition is not None:
                            conditions.append(condition)
                            continue
                    except Exception as e:
                        logger.debug(f"Could not evaluate full path condition '{cond_str}': {e}")
                
                # Parse simple conditions: "field operator value"
                operators = ['==', '!=', '>=', '<=', '>', '<', ' in ', ' not in ']
                for op in operators:
                    if op in cond_str:
                        parts = cond_str.split(op, 1)
                        if len(parts) == 2:
                            field_name = parts[0].strip()
                            value_str = parts[1].strip()
                            
                            if hasattr(model_class, field_name):
                                column_attr = getattr(model_class, field_name)
                                if isinstance(column_attr, InstrumentedAttribute):
                                    # Parse value
                                    value = self._parse_value(value_str)
                                    
                                    # Build condition based on operator
                                    if op == '==':
                                        conditions.append(column_attr == value)
                                    elif op == '!=':
                                        conditions.append(column_attr != value)
                                    elif op == '>':
                                        conditions.append(column_attr > value)
                                    elif op == '<':
                                        conditions.append(column_attr < value)
                                    elif op == '>=':
                                        conditions.append(column_attr >= value)
                                    elif op == '<=':
                                        conditions.append(column_attr <= value)
                                    elif op == ' in ':
                                        if isinstance(value, (list, tuple)):
                                            conditions.append(column_attr.in_(value))
                                        else:
                                            logger.warning(f"Value for 'in' operator must be a list or tuple: {value_str}")
                                    elif op == ' not in ':
                                        if isinstance(value, (list, tuple)):
                                            conditions.append(~column_attr.in_(value))
                                        else:
                                            logger.warning(f"Value for 'not in' operator must be a list or tuple: {value_str}")
                                    break
                        break
                        
            except Exception as e:
                logger.warning(f"Could not parse condition '{cond_str}': {e}")
        
        return conditions
    
    def _parse_value(self, value_str: str) -> Any:
        """Parse a value string to Python object"""
        value_str = value_str.strip()
        
        # Try to evaluate as Python literal
        try:
            return eval(value_str, {'__builtins__': {}})
        except:
            pass
        
        # Try to parse as string (remove quotes)
        if (value_str.startswith('"') and value_str.endswith('"')) or \
           (value_str.startswith("'") and value_str.endswith("'")):
            return value_str[1:-1]
        
        # Try common literals
        if value_str.lower() == 'true':
            return True
        if value_str.lower() == 'false':
            return False
        if value_str.lower() == 'none' or value_str.lower() == 'null':
            return None
        
        # Try to parse as number
        try:
            if '.' in value_str:
                return float(value_str)
            else:
                return int(value_str)
        except:
            pass
        
        # Return as string
        return value_str