#!/usr/bin/env python3
"""
NOT_IN 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 NotInExecutor(AUniversalOperationExecutor):
    """
    NOT_IN operation executor - Inverse membership test.
    Filters items where a field value is NOT in a specified set (like SQL NOT IN, MongoDB $nin).
    Converts values to set for O(1) lookup per item, O(n) total.
    Capability: Universal
    Operation Type: FILTERING
    """
    OPERATION_NAME = "NOT_IN"
    OPERATION_TYPE = OperationType.FILTERING
    SUPPORTED_NODE_TYPES = []  # Universal

    def _do_execute(self, action: QueryAction, context: ExecutionContext) -> ExecutionResult:
        """Execute NOT_IN operation."""
        params = action.params
        field = params.get('field')
        values = params.get('values', [])
        path = params.get('path', None)

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

        node = context.node
        result_data = self._execute_not_in(node, field, values, path, context)

        return ExecutionResult(
            success=True,
            data=result_data,
            action_type=self.OPERATION_NAME,
            metadata={'matched_count': len(result_data.get('items', []))}
        )

    def _execute_not_in(self, node: Any, field: str, values: list,
                        path: Optional[str], context: ExecutionContext) -> dict:
        """
        Execute NOT_IN inverse membership check.
        Converts exclusion values to a set for O(1) membership tests.
        Total complexity: O(n) where n = number of items.
        """
        # O(m) set construction where m = len(values)
        exclude_set = set(values) if values else set()

        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:
            value = extract_field_value(item, field) if field else item
            if value not in exclude_set:
                matched_items.append(item)

        return {
            'items': matched_items,
            'count': len(matched_items),
            'total_items': len(items),
            'excluded_values': list(values),
            'field': field
        }


__all__ = ['NotInExecutor']
