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

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

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

    def _execute_percentile(self, node: Any, params: dict[str, Any]) -> dict[str, Any]:
        items = extract_items(node)
        field: Optional[str] = params.get("field")
        percentile = float(params.get("percentile", 50.0))
        method = str(params.get("method", "linear")).lower()
        as_field = params.get("as_field", "percentile")

        percentile = min(100.0, max(0.0, percentile))
        if method not in {"linear", "nearest"}:
            method = "linear"

        values = self._extract_numeric_values(items, field)
        values.sort()
        result = self._compute_percentile(values, percentile, method)

        return {
            as_field: result,
            "percentile": percentile,
            "method": method,
            "count": len(values),
            "field": field,
        }

    def _extract_numeric_values(self, items: list[Any], field: Optional[str]) -> list[float]:
        values: list[float] = []
        for item in items:
            raw = extract_field_value(item, field) if field else item
            numeric = self._to_number(raw)
            if numeric is not None:
                values.append(numeric)
        return values

    def _compute_percentile(self, values: list[float], percentile: float, method: str) -> Optional[float]:
        n = len(values)
        if n == 0:
            return None
        if n == 1:
            return values[0]

        rank = (percentile / 100.0) * (n - 1)
        if method == "nearest":
            index = int(round(rank))
            return values[index]

        lower_idx = int(rank)
        upper_idx = min(lower_idx + 1, n - 1)
        fraction = rank - lower_idx
        lower = values[lower_idx]
        upper = values[upper_idx]
        return lower + (upper - lower) * fraction

    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__ = ["PercentileExecutor"]
