# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

"""
Input validation utilities for transformations.

This module provides utilities to detect and validate input types for
streaming and batch transformations.
"""

from typing import Any, List

from ..resources.locations import StreamChannelReference, StreamingInput

__all__ = [
    "is_streaming_input",
    "is_streaming_source",
    "normalize_streaming_input",
    "validate_streaming_transformation_inputs",
    "validate_batch_transformation_inputs",
]


def is_streaming_input(input_source: Any) -> bool:
    """
    Determine if an input source is a streaming input.

    Streaming inputs are sources that extend the StreamingInput base class,
    such as KafkaTopic.

    Args:
        input_source: The input source to check.

    Returns:
        bool: True if the input is a streaming input, False otherwise.
    """
    return isinstance(input_source, StreamingInput)


def is_streaming_source(source: Any) -> bool:
    """
    Determine if a source is a streaming source.

    A source is considered streaming if:
    1. It is a StreamingInput (e.g., KafkaTopic, FileStoreStreamingSource)
    2. It is a streaming transformation (has mode=STREAMING set explicitly)

    Note: Transformations are either batch OR streaming - they cannot have a mixture
    of streaming and batch sources. A streaming transformation requires all inputs
    to be streaming sources; a batch transformation requires all inputs to be batch.

    This function is used by FeatureBuilder.from_dataset() and from_stream()
    to validate that the correct method is being used for the source type.

    Args:
        source: The source object to check (can be a StreamingInput,
                SubscriptableTransformation, or any other source type).

    Returns:
        bool: True if the source is a streaming source, False otherwise.
    """
    # Import here to avoid circular imports
    from ..resources.transformations import TransformationMode

    # Direct streaming input (e.g., KafkaTopic, FileStoreStreamingSource)
    if is_streaming_input(source):
        return True

    # Check if this is a streaming transformation (SubscriptableTransformation)
    # Streaming transformations are created via streaming_sql_transformation or
    # streaming_df_transformation, which set mode=STREAMING explicitly.
    if hasattr(source, "transformation"):
        decorator = source.transformation
        # Check for explicit mode field (preferred method)
        if hasattr(decorator, "mode"):
            return decorator.mode == TransformationMode.STREAMING

        # Fallback: Check func_params_to_inputs for backwards compatibility
        # This handles older code that may not have the mode field
        if hasattr(decorator, "func_params_to_inputs"):
            inputs = list(decorator.func_params_to_inputs.values())
            # A streaming transformation has at least one input and all are streaming
            if inputs and all(is_streaming_input(inp) for inp in inputs):
                return True

    return False


def normalize_streaming_input(inp: Any) -> StreamingInput:
    """
    Normalize a streaming input to a StreamingInput object.

    Accepts:
    - StreamingInput object (KafkaTopic, FileStoreStreamingSource, StreamChannelReference)
    - String (stream channel name) - converted to StreamChannelReference

    Rejects:
    - Tuples (name, variant) - streaming inputs cannot have variants

    Args:
        inp: The input to normalize.

    Returns:
        StreamingInput: A normalized StreamingInput object.

    Raises:
        ValueError: If the input is a tuple (variants not allowed) or invalid type.
    """
    if isinstance(inp, StreamingInput):
        return inp

    if isinstance(inp, str):
        # Name-only input - wrap in StreamChannelReference
        return StreamChannelReference(name=inp)

    if isinstance(inp, tuple):
        # Reject tuples - streaming inputs cannot have variants
        if len(inp) >= 2:
            raise ValueError(
                f"Streaming inputs cannot have variants. "
                f"Pass the stream channel name as a string: '{inp[0]}' "
                f"instead of ('{inp[0]}', '{inp[1]}'). "
                f"Stream channels always use a fixed internal variant."
            )
        raise ValueError(f"Invalid streaming input tuple: {inp}")

    raise ValueError(
        f"Invalid streaming input type: {type(inp).__name__}. "
        f"Expected a StreamingInput object (e.g., KafkaTopic) or a string name."
    )


def validate_streaming_transformation_inputs(inputs: List[Any]) -> List[Any]:
    """
    Validate and normalize inputs for a streaming transformation.

    Streaming transformations require all inputs to be streaming inputs.
    String inputs are automatically converted to StreamChannelReference.
    Tuple inputs (name, variant) are rejected - streaming inputs have no variants.

    Args:
        inputs: List of input sources for the transformation.

    Returns:
        The validated and normalized list of streaming inputs.

    Raises:
        ValueError: If no inputs are provided, if tuples are used, or if invalid types found.
    """
    if not inputs:
        raise ValueError(
            "Streaming transformations require at least one input. "
            "No inputs were provided."
        )

    normalized = []
    for inp in inputs:
        normalized.append(normalize_streaming_input(inp))

    return normalized


def validate_batch_transformation_inputs(inputs: List[Any]) -> List[Any]:
    """
    Validate inputs for a batch transformation.

    Batch transformations should not have streaming inputs.

    Args:
        inputs: List of input sources for the transformation.

    Returns:
        The validated list of inputs.

    Raises:
        ValueError: If any streaming inputs are found.
    """
    if not inputs:
        return inputs

    streaming_inputs = [inp for inp in inputs if is_streaming_input(inp)]

    if streaming_inputs:
        streaming_count = len(streaming_inputs)
        raise ValueError(
            f"Batch transformations cannot have streaming inputs. "
            f"Found {streaming_count} streaming input(s). "
            f"Use streaming_sql_transformation or streaming_df_transformation instead."
        )

    return inputs
