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

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 AllMatchExecutor(AUniversalOperationExecutor):
    """Filter items where all elements in an array field match condition. O(n * a)."""

    OPERATION_NAME = "ALL_MATCH"
    OPERATION_TYPE = OperationType.FILTERING
    SUPPORTED_NODE_TYPES = []

    def _do_execute(self, action: QueryAction, context: ExecutionContext) -> ExecutionResult:
        params = action.params
        field = params.get("field")
        condition = params.get("condition")
        path = params.get("path", None)

        if not field:
            return ExecutionResult(
                success=False,
                data=None,
                error=f"ALL_MATCH requires 'field'. Got: {action.params}",
                action_type=self.OPERATION_NAME,
            )

        if condition is None:
            return ExecutionResult(
                success=False,
                data=None,
                error=f"ALL_MATCH requires 'condition'. Got: {action.params}",
                action_type=self.OPERATION_NAME,
            )

        result_data = self._execute_all_match(context.node, field, condition, path)
        return ExecutionResult(
            success=True,
            data=result_data,
            action_type=self.OPERATION_NAME,
            metadata={"matched_count": len(result_data.get("items", []))},
        )

    @staticmethod
    def _evaluate_condition(element: Any, condition: Any) -> bool:
        if isinstance(condition, dict):
            op = str(condition.get("operator", condition.get("op", "=="))).lower()
            expected = condition.get("value", condition.get("target", None))
            element_field = condition.get("field")
            case_sensitive = condition.get("case_sensitive", True)

            actual = extract_field_value(element, element_field) if element_field else element

            if op in ("==", "eq"):
                return actual == expected
            if op in ("!=", "ne"):
                return actual != expected
            if op in (">", "gt"):
                return actual > expected
            if op in (">=", "gte"):
                return actual >= expected
            if op in ("<", "lt"):
                return actual < expected
            if op in ("<=", "lte"):
                return actual <= expected
            if op in ("in",):
                return actual in expected
            if op in ("not_in", "not in"):
                return actual not in expected
            if op in ("contains",):
                if actual is None:
                    return False
                left = str(actual)
                right = str(expected)
                if not case_sensitive:
                    left = left.lower()
                    right = right.lower()
                return right in left
            if op in ("starts_with", "startswith"):
                if actual is None:
                    return False
                left = str(actual)
                right = str(expected)
                if not case_sensitive:
                    left = left.lower()
                    right = right.lower()
                return left.startswith(right)
            if op in ("ends_with", "endswith"):
                if actual is None:
                    return False
                left = str(actual)
                right = str(expected)
                if not case_sensitive:
                    left = left.lower()
                    right = right.lower()
                return left.endswith(right)

            return False

        return element == condition

    def _execute_all_match(
        self,
        node: Any,
        field: str,
        condition: Any,
        path: Optional[str],
    ) -> dict:
        if path:
            try:
                data = node.get(path, default=None)
            except Exception:
                data = None
        else:
            data = node

        items = extract_items(data)
        matched_items = []

        for item in items:
            array_value = extract_field_value(item, field)
            if not isinstance(array_value, (list, tuple, set)):
                continue
            if all(self._evaluate_condition(element, condition) for element in array_value):
                matched_items.append(item)

        return {
            "items": matched_items,
            "count": len(matched_items),
            "total_items": len(items),
            "field": field,
            "condition": condition,
        }


__all__ = ["AllMatchExecutor"]
