"""Tests for workflow output type casting.

This module tests that workflow outputs are automatically cast to match the
expected output types, just like node inputs are cast to expected types.
"""

import pytest

from workflow_engine import (
    Edge,
    FloatValue,
    InputNode,
    IntegerValue,
    JSONValue,
    OutputNode,
    SequenceValue,
    Workflow,
    WorkflowExecutionResultStatus,
)
from workflow_engine.contexts import InMemoryContext
from workflow_engine.core.values import resolve_path
from workflow_engine.execution.parallel import ParallelExecutionAlgorithm
from workflow_engine.execution.topological import TopologicalExecutionAlgorithm
from workflow_engine.files import JSONLinesFileValue
from workflow_engine.nodes import AddNode, ConstantIntegerNode, ConstantStringNode


@pytest.mark.unit
@pytest.mark.asyncio
async def test_basic_output_casting():
    """Test that IntegerValue is cast to FloatValue in workflow output."""
    input_node = InputNode.empty()
    output_node = OutputNode.from_fields(result=FloatValue)

    node = ConstantIntegerNode.from_value(id="producer", value=42)

    # Workflow expects FloatValue output, but node produces IntegerValue
    workflow = Workflow(
        input_node=input_node,
        output_node=output_node,
        inner_nodes=[node],
        edges=[
            Edge.from_nodes(
                source=node,
                source_key="value",
                target=output_node,
                target_key="result",
            )
        ],
    )

    context = InMemoryContext()
    algorithm = TopologicalExecutionAlgorithm()

    result = await algorithm.execute(context=context, workflow=workflow, input={})

    assert result.status is WorkflowExecutionResultStatus.SUCCESS

    result_value = result.output["result"]
    assert isinstance(result_value, FloatValue)
    assert result_value == 42.0


@pytest.mark.unit
@pytest.mark.asyncio
async def test_multiple_outputs_casting():
    """Test that multiple outputs are cast correctly in parallel."""
    input_node = InputNode.empty()
    output_node = OutputNode.from_fields(
        int_result=FloatValue,
        str_result=IntegerValue,
    )

    int_node = ConstantIntegerNode.from_value(id="int_producer", value=100)
    str_node = ConstantStringNode.from_value(id="str_producer", value="123")

    workflow = Workflow(
        input_node=input_node,
        output_node=output_node,
        inner_nodes=[int_node, str_node],
        edges=[
            Edge.from_nodes(
                source=int_node,
                source_key="value",
                target=output_node,
                target_key="int_result",
            ),
            Edge.from_nodes(
                source=str_node,
                source_key="value",
                target=output_node,
                target_key="str_result",
            ),
        ],
    )

    context = InMemoryContext()
    algorithm = TopologicalExecutionAlgorithm()

    result = await algorithm.execute(context=context, workflow=workflow, input={})
    assert result.status is WorkflowExecutionResultStatus.SUCCESS

    int_result = result.output["int_result"]
    assert isinstance(int_result, FloatValue)
    assert int_result == 100.0

    str_result = result.output["str_result"]
    assert isinstance(str_result, IntegerValue)
    assert str_result == 123


@pytest.mark.unit
@pytest.mark.asyncio
async def test_parallel_execution_algorithm():
    """Test that output casting works with ParallelExecutionAlgorithm."""
    input_node = InputNode.empty()
    output_node = OutputNode.from_fields(
        result=FloatValue,
    )

    node = ConstantIntegerNode.from_value(id="producer", value=42)

    workflow = Workflow(
        input_node=input_node,
        output_node=output_node,
        inner_nodes=[node],
        edges=[
            Edge.from_nodes(
                source=node,
                source_key="value",
                target=output_node,
                target_key="result",
            )
        ],
    )

    context = InMemoryContext()
    algorithm = ParallelExecutionAlgorithm()

    result = await algorithm.execute(context=context, workflow=workflow, input={})

    assert result.status is WorkflowExecutionResultStatus.SUCCESS
    assert isinstance(result.output["result"], FloatValue)
    assert result.output["result"] == 42.0


@pytest.mark.unit
@pytest.mark.asyncio
async def test_complex_type_sequence_to_jsonlines():
    """Test casting complex types like SequenceValue[JSONValue] to JSONLinesFileValue."""
    # Create a sequence of JSON values
    json_values = [JSONValue({"name": "Alice"}), JSONValue({"name": "Bob"})]
    sequence = SequenceValue(json_values)

    # Test that the sequence can be cast to JSONLinesFileValue
    context = InMemoryContext()

    # Verify the cast is possible
    assert sequence.can_cast_to(JSONLinesFileValue)

    # Perform the cast
    result = await sequence.cast_to(JSONLinesFileValue, context=context)

    assert isinstance(result, JSONLinesFileValue)

    # Verify the result has a path (may be relative or absolute depending on context)
    assert result.path is not None
    assert len(result.path) > 0


@pytest.mark.unit
@pytest.mark.asyncio
async def test_no_casting_when_types_match():
    """Test that no casting occurs when output types already match."""
    input_node = InputNode.empty()
    output_node = OutputNode.from_fields(result=IntegerValue)

    node = ConstantIntegerNode.from_value(id="producer", value=42)

    workflow = Workflow(
        input_node=input_node,
        output_node=output_node,
        inner_nodes=[node],
        edges=[
            Edge.from_nodes(
                source=node,
                source_key="value",
                target=output_node,
                target_key="result",
            )
        ],
    )

    context = InMemoryContext()
    algorithm = TopologicalExecutionAlgorithm()

    result = await algorithm.execute(context=context, workflow=workflow, input={})

    assert result.status is WorkflowExecutionResultStatus.SUCCESS
    assert isinstance(result.output["result"], IntegerValue)
    assert result.output["result"] == 42


@pytest.mark.unit
@pytest.mark.asyncio
async def test_input_casting():
    """Test that workflow inputs can be cast to expected types."""
    input_node = InputNode.from_fields(
        a=FloatValue,
        b=FloatValue,
    )
    output_node = OutputNode.from_fields(
        result=FloatValue,
    )

    add_node = AddNode(id="add")

    workflow = Workflow(
        input_node=input_node,
        output_node=output_node,
        inner_nodes=[add_node],
        edges=[
            Edge.from_nodes(
                source=input_node,
                source_key="a",
                target=add_node,
                target_key="a",
            ),
            Edge.from_nodes(
                source=input_node,
                source_key="b",
                target=add_node,
                target_key="b",
            ),
            Edge.from_nodes(
                source=add_node,
                source_key="sum",
                target=output_node,
                target_key="result",
            ),
        ],
    )

    # Workflow should expect FloatValue inputs
    assert issubclass(workflow.input_fields["a"][0], FloatValue)
    assert issubclass(workflow.input_fields["b"][0], FloatValue)

    context = InMemoryContext()
    algorithm = TopologicalExecutionAlgorithm()

    result = await algorithm.execute(
        context=context,
        workflow=workflow,
        input={"a": FloatValue(10.5), "b": FloatValue(20.3)},
    )

    assert result.status is WorkflowExecutionResultStatus.SUCCESS


@pytest.mark.unit
def test_workflow_output_type_inference():
    """Test that workflow infers output types from OutputNode."""
    input_node = InputNode.empty()
    output_node = OutputNode.from_fields(
        result=IntegerValue,
    )

    node = ConstantIntegerNode.from_value(id="producer", value=42)

    workflow = Workflow(
        input_node=input_node,
        output_node=output_node,
        inner_nodes=[node],
        edges=[
            Edge.from_nodes(
                source=node,
                source_key="value",
                target=output_node,
                target_key="result",
            )
        ],
    )
    assert issubclass(
        resolve_path(data_type=workflow.output_type, path=["result"]),
        IntegerValue,
    )
