#!/usr/bin/env python3
"""
REDUCE 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 ReduceExecutor(AUniversalOperationExecutor):
    """REDUCE operation executor."""

    OPERATION_NAME = "REDUCE"
    OPERATION_TYPE = OperationType.AGGREGATION
    SUPPORTED_NODE_TYPES = []

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

    def _execute_reduce(self, node: Any, params: dict[str, Any]) -> dict[str, Any]:
        items = extract_items(node)
        field: Optional[str] = params.get("field")
        operation = str(params.get("operation", "sum")).lower()
        as_field = params.get("as_field", "reduce")
        accumulator = params.get("initial")

        for item in items:
            value = extract_field_value(item, field) if field else item
            accumulator = self._apply_operation(accumulator, value, operation)

        return {
            as_field: accumulator,
            "operation": operation,
            "field": field,
        }

    def _apply_operation(self, accumulator: Any, value: Any, operation: str) -> Any:
        if value is None:
            return accumulator

        if operation == "sum":
            number = self._to_number(value)
            if number is None:
                return accumulator
            return (0.0 if accumulator is None else float(accumulator)) + number

        if operation == "product":
            number = self._to_number(value)
            if number is None:
                return accumulator
            return (1.0 if accumulator is None else float(accumulator)) * number

        if operation == "concat":
            text = str(value)
            return text if accumulator is None else f"{accumulator}{text}"

        if operation == "min":
            if accumulator is None:
                return value
            return value if value < accumulator else accumulator

        if operation == "max":
            if accumulator is None:
                return value
            return value if value > accumulator else accumulator

        return accumulator

    def _to_number(self, value: Any) -> Optional[float]:
        if isinstance(value, bool):
            return None
        if isinstance(value, (int, float)):
            return float(value)
        if isinstance(value, str):
            try:
                return float(value)
            except ValueError:
                return None
        return None


__all__ = ["ReduceExecutor"]
