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

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


_COST_ESTIMATES = {
    'SELECT': 1.0,
    'INSERT': 1.0,
    'UPDATE': 2.0,
    'DELETE': 2.0,
    'WHERE': 1.5,
    'FILTER': 1.5,
    'IN': 1.2,
    'NOT_IN': 1.2,
    'EXISTS': 1.0,
    'IS_NULL': 1.0,
    'LIKE': 2.0,
    'BETWEEN': 1.5,
    'JOIN': 5.0,
    'GROUP BY': 3.0,
    'ORDER BY': 3.0,
    'DISTINCT': 2.5,
    'UPSERT': 2.5,
    'TRUNCATE': 0.5,
}


class ExplainExecutor(AUniversalOperationExecutor):
    """
    EXPLAIN operation executor - Universal operation.
    Returns query execution plan analysis without executing the query.
    Provides estimated cost, operation count, data sources, and optimization hints.
    Capability: Universal
    Operation Type: CORE
    """
    OPERATION_NAME = "EXPLAIN"
    OPERATION_TYPE = OperationType.CORE
    SUPPORTED_NODE_TYPES = []  # Universal

    def _do_execute(self, action: QueryAction, context: ExecutionContext) -> ExecutionResult:
        """Execute EXPLAIN operation."""
        params = action.params
        query = params.get('query', {})
        verbose = params.get('verbose', False)

        node = context.node
        result_data = self._execute_explain(node, query, verbose, context)

        return ExecutionResult(
            success=True,
            data=result_data,
            action_type=self.OPERATION_NAME,
            metadata={
                'verbose': verbose,
                'operations_count': result_data.get('operations_count', 0)
            }
        )

    def _execute_explain(self, node: Any, query: Any, verbose: bool,
                         context: ExecutionContext) -> dict:
        """
        Analyze query plan without executing.
        Produces estimated_cost, operations_count, data_sources, optimization_hints.
        """
        operations = []
        data_sources = set()
        total_cost = 0.0
        optimization_hints = []

        # Parse actions from the query
        actions = self._extract_actions(query)

        for act in actions:
            op_name = act.get('action', act.get('operation', 'UNKNOWN')).upper()
            target = act.get('target', act.get('params', {}).get('target'))
            if target:
                data_sources.add(target)

            cost = _COST_ESTIMATES.get(op_name, 1.0)
            total_cost += cost

            op_info = {'operation': op_name, 'estimated_cost': cost}
            if verbose:
                op_info['params'] = act.get('params', {})
            operations.append(op_info)

        # Factor in data size if node is available
        items = extract_items(node) if node is not None else []
        data_size = len(items)
        if data_size > 0:
            total_cost *= (1.0 + 0.1 * (data_size / 1000))

        # Generate optimization hints
        optimization_hints = self._generate_hints(actions, data_size)

        result = {
            'estimated_cost': round(total_cost, 3),
            'operations_count': len(operations),
            'data_sources': sorted(data_sources),
            'optimization_hints': optimization_hints,
            'data_size': data_size,
        }

        if verbose:
            result['operations'] = operations
            result['cost_breakdown'] = {
                op['operation']: op['estimated_cost'] for op in operations
            }

        return result

    @staticmethod
    def _extract_actions(query: Any) -> list[dict]:
        """Extract action descriptors from various query representations."""
        if isinstance(query, list):
            return [a if isinstance(a, dict) else {'action': str(a)} for a in query]
        if isinstance(query, dict):
            if 'actions' in query:
                return query['actions'] if isinstance(query['actions'], list) else [query['actions']]
            return [query]
        if isinstance(query, str):
            return [{'action': query}]
        if hasattr(query, 'actions'):
            actions = query.actions
            return actions if isinstance(actions, list) else [actions]
        return []

    @staticmethod
    def _generate_hints(actions: list[dict], data_size: int) -> list[str]:
        """Generate optimization hints based on query structure and data size."""
        hints = []
        op_names = [a.get('action', a.get('operation', '')).upper() for a in actions]

        if data_size > 10000:
            hints.append("Large dataset detected; consider adding indexes or using LIMIT.")

        filter_ops = {'WHERE', 'FILTER', 'IN', 'NOT_IN', 'BETWEEN', 'LIKE', 'EXISTS', 'IS_NULL'}
        has_filter = any(op in filter_ops for op in op_names)
        has_aggregation = any(op in ('GROUP BY', 'SUM', 'AVG', 'COUNT') for op in op_names)

        if has_aggregation and not has_filter:
            hints.append("Aggregation without filtering; consider adding WHERE to reduce data first.")

        if op_names.count('WHERE') > 1:
            hints.append("Multiple WHERE clauses detected; consider combining into a single filter.")

        if 'ORDER BY' in op_names and 'LIMIT' not in op_names:
            hints.append("ORDER BY without LIMIT; consider adding LIMIT to avoid full sort.")

        if not hints:
            hints.append("No obvious optimizations detected.")

        return hints


__all__ = ['ExplainExecutor']
