"""
Validation engine for LogicPwn Exploit Engine.
- Validates response content, status, headers, and session state
- Extensible and interoperable with core models
"""
from typing import Any, Dict
from logicpwn.core.utils.utils import check_indicators
from .models import ExploitStep, ValidationResult

try:
    from jsonpath_ng import parse as jsonpath_parse
except ImportError:
    jsonpath_parse = None

def eval_python_expr(expr: str, response: Any, session_state: dict) -> bool:
    """
    Safely evaluate Python expressions using AST parsing instead of eval().
    
    Args:
        expr: Python expression to evaluate
        response: HTTP response object
        session_state: Current session state
    
    Returns:
        bool: Result of expression evaluation, False on any error
    """
    try:
        # Import here to avoid circular imports and ensure availability
        import ast
        import operator
        
        # Define safe operations
        SAFE_OPERATORS = {
            ast.Add: operator.add,
            ast.Sub: operator.sub,
            ast.Mult: operator.mul,
            ast.Div: operator.truediv,
            ast.FloorDiv: operator.floordiv,
            ast.Mod: operator.mod,
            ast.Pow: operator.pow,
            ast.LShift: operator.lshift,
            ast.RShift: operator.rshift,
            ast.BitOr: operator.or_,
            ast.BitXor: operator.xor,
            ast.BitAnd: operator.and_,
            ast.Eq: operator.eq,
            ast.NotEq: operator.ne,
            ast.Lt: operator.lt,
            ast.LtE: operator.le,
            ast.Gt: operator.gt,
            ast.GtE: operator.ge,
            ast.Is: operator.is_,
            ast.IsNot: operator.is_not,
            ast.In: lambda x, y: x in y,  # Fixed: x in y, not operator.contains(y, x)
            ast.NotIn: lambda x, y: x not in y,  # Fixed: x not in y
        }
        
        SAFE_FUNCTIONS = {
            'len': len,
            'str': str,
            'int': int,
            'float': float,
            'bool': bool,
            'abs': abs,
            'min': min,
            'max': max,
            'hasattr': hasattr,
            'getattr': getattr,
            'isinstance': isinstance,
        }
        
        # Parse the expression
        tree = ast.parse(expr, mode='eval')
        
        def _eval_node(node):
            if isinstance(node, ast.Expression):
                return _eval_node(node.body)
            elif isinstance(node, ast.Constant):  # Python 3.8+
                return node.value
            elif isinstance(node, (ast.Str, ast.Num)):  # Python < 3.8
                return node.n if isinstance(node, ast.Num) else node.s
            elif isinstance(node, ast.Name):
                # Allow access to safe variables
                if node.id == 'response':
                    return response
                elif node.id == 'session':
                    return session_state
                elif node.id in SAFE_FUNCTIONS:
                    return SAFE_FUNCTIONS[node.id]
                else:
                    raise ValueError(f"Undefined variable: {node.id}")
            elif isinstance(node, ast.Attribute):
                # Allow safe attribute access
                value = _eval_node(node.value)
                if hasattr(value, node.attr):
                    return getattr(value, node.attr)
                else:
                    raise ValueError(f"Attribute not found: {node.attr}")
            elif isinstance(node, ast.Compare):
                left = _eval_node(node.left)
                for op, right in zip(node.ops, node.comparators):
                    if type(op) in SAFE_OPERATORS:
                        right_val = _eval_node(right)
                        left = SAFE_OPERATORS[type(op)](left, right_val)
                    else:
                        raise ValueError(f"Unsafe operation: {type(op)}")
                return left
            elif isinstance(node, ast.BinOp):
                if type(node.op) in SAFE_OPERATORS:
                    left = _eval_node(node.left)
                    right = _eval_node(node.right)
                    return SAFE_OPERATORS[type(node.op)](left, right)
                else:
                    raise ValueError(f"Unsafe operation: {type(node.op)}")
            elif isinstance(node, ast.Call):
                func = _eval_node(node.func)
                if func in SAFE_FUNCTIONS.values():
                    args = [_eval_node(arg) for arg in node.args]
                    return func(*args)
                else:
                    raise ValueError(f"Unsafe function call")
            else:
                raise ValueError(f"Unsafe node type: {type(node)}")
        
        result = _eval_node(tree)
        return bool(result)
        
    except (SyntaxError, ValueError, TypeError, AttributeError) as e:
        # Log specific error types for debugging
        import logging
        logging.debug(f"Python expression evaluation failed: {expr} - {type(e).__name__}: {e}")
        return False
    except Exception as e:
        # Catch any other unexpected errors
        import logging
        logging.warning(f"Unexpected error in Python expression evaluation: {expr} - {e}")
        return False

def eval_jsonpath(expr: str, json_body: dict) -> bool:
    """
    Safely evaluate JSONPath expressions with enhanced error handling.
    
    Args:
        expr: JSONPath expression to evaluate
        json_body: JSON data to search
    
    Returns:
        bool: True if path exists and has values, False otherwise
    """
    if not jsonpath_parse or not json_body:
        return False
    
    try:
        # Validate JSONPath expression syntax
        if not expr or not expr.strip():
            return False
        
        # Basic security check for JSONPath injection
        if any(dangerous in expr for dangerous in ['__', 'eval', 'exec']):
            return False
        
        jsonpath_expr = jsonpath_parse(expr)
        matches = [match.value for match in jsonpath_expr.find(json_body)]
        
        # Return True if matches exist and are not None/empty
        return bool(matches) and any(match is not None for match in matches)
        
    except Exception as e:
        # Log parsing errors for debugging
        import logging
        logging.debug(f"JSONPath evaluation failed: {expr} - {e}")
        return False

def validate_step_success(
    response: Any,
    step: ExploitStep,
    session_state: Dict[str, Any]
) -> ValidationResult:
    """
    Validate if exploit step succeeded based on response.
    Checks:
    - HTTP status codes
    - Response headers
    - Body content patterns
    - JSON field values
    - Session state changes
    """
    reasons = []
    matched, failed = [], []
    body = getattr(response, 'text', None)
    if hasattr(response, 'json'):
        try:
            json_body = response.json()
        except Exception:
            json_body = None
    else:
        json_body = None
    # Success indicators
    is_valid, matched = check_indicators(body or '', step.success_indicators, "success")
    # Failure indicators
    is_fail, failed = check_indicators(body or '', step.failure_indicators, "failure")
    # Status code check
    status_code = getattr(response, 'status_code', None)
    # Advanced validation
    for ind in step.success_indicators:
        if ind.startswith("status_code =="):
            try:
                code = int(ind.split("==")[1].strip())
                if status_code == code:
                    is_valid = True
                    matched.append(ind)
            except Exception:
                pass
        elif ind.startswith("status_code !="):
            try:
                code = int(ind.split("!=")[1].strip())
                if status_code != code:
                    is_valid = True
                    matched.append(ind)
            except Exception:
                pass
        elif ind.startswith("py:"):
            expr = ind[3:]
            if eval_python_expr(expr, response, session_state):
                is_valid = True
                matched.append(ind)
        elif ind.startswith("jsonpath:") and json_body:
            expr = ind[9:]
            if eval_jsonpath(expr, json_body):
                is_valid = True
                matched.append(ind)
        elif ind.startswith("session:"):
            expr = ind[8:]
            try:
                if eval(expr, {"session": session_state}):
                    is_valid = True
                    matched.append(ind)
            except Exception:
                pass
        elif ind.endswith("in response.json") and json_body:
            field = ind.split("in response.json")[0].strip()
            if field in json_body:
                is_valid = True
                matched.append(ind)
    # Failure indicators override
    if is_fail:
        is_valid = False
        reasons.extend([f"Failure indicator matched: {f}" for f in failed])
    if not is_valid:
        reasons.append("No success indicators matched.")
    return ValidationResult(
        is_valid=is_valid,
        reasons=reasons,
        matched_indicators=matched,
        failed_indicators=failed,
        extracted_data={}
    ) 