#!/usr/bin/env python3
"""
CASE Executor
Company: eXonware.com
Author: eXonware Backend Team
Email: connect@exonware.com
Version: 0.0.1.8
"""

from typing import Any, Optional
from ..base import AUniversalOperationExecutor
from ....contracts import QueryAction, ExecutionContext, ExecutionResult
from ....defs import OperationType
from ..utils import extract_items, extract_field_value


class CaseExecutor(AUniversalOperationExecutor):
    """
    CASE operation executor - SQL CASE/WHEN/THEN/ELSE logic.

    Evaluates a list of conditions per item and returns the value
    corresponding to the first matching condition, or a fallback else_value.

    params:
        conditions: list of {when: predicate, then: value}
        else_value: fallback when no condition matches
        field: output field name for the result (default: '_case_result')

    Time Complexity: O(n*m) where n=items, m=conditions
    Capability: Universal
    Operation Type: ADVANCED (conditional)
    """
    OPERATION_NAME = "CASE"
    OPERATION_TYPE = OperationType.ADVANCED
    SUPPORTED_NODE_TYPES = []

    def _do_execute(self, action: QueryAction, context: ExecutionContext) -> ExecutionResult:
        params = action.params
        node = context.node
        result_data = self._execute_op(node, params, context)
        return ExecutionResult(
            success=True,
            data=result_data,
            action_type=self.OPERATION_NAME,
            metadata={'operation': self.OPERATION_NAME}
        )

    def _execute_op(self, node: Any, params: dict, context: ExecutionContext) -> dict:
        items = extract_items(node)
        conditions = params.get('conditions', [])
        else_value = params.get('else_value', None)
        field = params.get('field', '_case_result')

        result_items = []
        matched_count = 0

        for item in items:
            item_copy = item.copy() if isinstance(item, dict) else {'value': item}
            resolved = self._resolve_case(item, conditions, else_value)
            if resolved is not _SENTINEL:
                matched_count += 1
            item_copy[field] = resolved if resolved is not _SENTINEL else else_value
            result_items.append(item_copy)

        return {
            'items': result_items,
            'count': len(result_items),
            'total_items': len(items),
            'matched_count': matched_count,
        }

    def _resolve_case(self, item: Any, conditions: list, else_value: Any) -> Any:
        """Evaluate conditions in order; return the `then` value of the first match."""
        for cond in conditions:
            when = cond.get('when', {})
            then_val = cond.get('then', None)
            if self._predicate_matches(item, when):
                return then_val
        return _SENTINEL

    def _predicate_matches(self, item: Any, predicate: Any) -> bool:
        """
        Evaluate a predicate against an item.

        Supported predicate formats:
        - dict with 'field'/'operator'/'value' keys  (expression style)
        - dict with plain {field: value} pairs        (equality match)
        - callable                                    (lambda / function)
        """
        if predicate is None or predicate == {}:
            return True

        if callable(predicate):
            try:
                return bool(predicate(item))
            except Exception:
                return False

        if isinstance(predicate, dict):
            if 'field' in predicate and 'operator' in predicate:
                return self._eval_expr(item, predicate)
            if isinstance(item, dict):
                return all(item.get(k) == v for k, v in predicate.items())
            return all(getattr(item, k, None) == v for k, v in predicate.items())

        return bool(predicate)

    @staticmethod
    def _eval_expr(item: Any, expr: dict) -> bool:
        field = expr['field']
        op = expr['operator']
        expected = expr['value']
        actual = extract_field_value(item, field) if isinstance(item, dict) or hasattr(item, '__getattr__') else None
        if actual is None and isinstance(item, dict):
            actual = item.get(field)

        ops = {
            '==': lambda a, b: a == b,
            '!=': lambda a, b: a != b,
            '>': lambda a, b: a > b,
            '>=': lambda a, b: a >= b,
            '<': lambda a, b: a < b,
            '<=': lambda a, b: a <= b,
            'in': lambda a, b: a in b,
            'not in': lambda a, b: a not in b,
            'contains': lambda a, b: b in a,
        }
        fn = ops.get(op)
        if fn is None:
            return False
        try:
            return fn(actual, expected)
        except (TypeError, ValueError):
            return False


_SENTINEL = object()

__all__ = ['CaseExecutor']
