"""
Tests for BaseState and reducer functions.
"""

from typing import Annotated, Any

from torivers_sdk.automation.state import (
    BaseState,
    append_list,
    last_value,
    merge_dict,
)


class TestMergeDictReducer:
    """Test suite for merge_dict reducer function."""

    def test_merge_empty_dicts(self) -> None:
        """Test merging two empty dicts."""
        result = merge_dict({}, {})
        assert result == {}

    def test_merge_left_empty(self) -> None:
        """Test merging when left dict is empty."""
        result = merge_dict({}, {"a": 1, "b": 2})
        assert result == {"a": 1, "b": 2}

    def test_merge_right_empty(self) -> None:
        """Test merging when right dict is empty."""
        result = merge_dict({"a": 1, "b": 2}, {})
        assert result == {"a": 1, "b": 2}

    def test_merge_no_overlap(self) -> None:
        """Test merging dicts with no overlapping keys."""
        result = merge_dict({"a": 1}, {"b": 2})
        assert result == {"a": 1, "b": 2}

    def test_merge_with_overlap(self) -> None:
        """Test that right dict values overwrite left dict values."""
        result = merge_dict({"a": 1, "b": 2}, {"b": 3, "c": 4})
        assert result == {"a": 1, "b": 3, "c": 4}

    def test_merge_nested_dicts(self) -> None:
        """Test merging dicts with nested dict values."""
        left = {"config": {"key1": "value1"}}
        right = {"config": {"key2": "value2"}}
        result = merge_dict(left, right)
        # Shallow merge - right replaces left completely for same key
        assert result == {"config": {"key2": "value2"}}

    def test_merge_preserves_types(self) -> None:
        """Test that merge preserves different value types."""
        left = {"str": "hello", "num": 42}
        right = {"list": [1, 2, 3], "bool": True}
        result = merge_dict(left, right)
        assert result == {"str": "hello", "num": 42, "list": [1, 2, 3], "bool": True}

    def test_merge_original_unchanged(self) -> None:
        """Test that original dicts are not mutated."""
        left = {"a": 1}
        right = {"b": 2}
        left_copy = left.copy()
        right_copy = right.copy()
        merge_dict(left, right)
        assert left == left_copy
        assert right == right_copy


class TestAppendListReducer:
    """Test suite for append_list reducer function."""

    def test_append_empty_lists(self) -> None:
        """Test appending two empty lists."""
        result = append_list([], [])
        assert result == []

    def test_append_left_empty(self) -> None:
        """Test appending when left list is empty."""
        result = append_list([], [1, 2, 3])
        assert result == [1, 2, 3]

    def test_append_right_empty(self) -> None:
        """Test appending when right list is empty."""
        result = append_list([1, 2, 3], [])
        assert result == [1, 2, 3]

    def test_append_both_have_items(self) -> None:
        """Test appending two non-empty lists."""
        result = append_list([1, 2], [3, 4])
        assert result == [1, 2, 3, 4]

    def test_append_mixed_types(self) -> None:
        """Test appending lists with different element types."""
        result = append_list(["a", "b"], [1, 2])
        assert result == ["a", "b", 1, 2]

    def test_append_nested_lists(self) -> None:
        """Test appending lists containing lists."""
        result = append_list([[1, 2]], [[3, 4]])
        assert result == [[1, 2], [3, 4]]

    def test_append_dicts(self) -> None:
        """Test appending lists of dicts (e.g., errors)."""
        result = append_list(
            [{"error": "first"}],
            [{"error": "second"}],
        )
        assert result == [{"error": "first"}, {"error": "second"}]

    def test_append_original_unchanged(self) -> None:
        """Test that original lists are not mutated."""
        left = [1, 2]
        right = [3, 4]
        left_copy = left.copy()
        right_copy = right.copy()
        append_list(left, right)
        assert left == left_copy
        assert right == right_copy


class TestLastValueReducer:
    """Test suite for last_value reducer function."""

    def test_last_value_basic(self) -> None:
        """Test that right value is returned."""
        result = last_value("old", "new")
        assert result == "new"

    def test_last_value_with_none_right(self) -> None:
        """Test with None as right value."""
        result = last_value("old", None)
        assert result is None

    def test_last_value_with_none_left(self) -> None:
        """Test with None as left value."""
        result = last_value(None, "new")
        assert result == "new"

    def test_last_value_both_none(self) -> None:
        """Test with both values as None."""
        result = last_value(None, None)
        assert result is None

    def test_last_value_same_value(self) -> None:
        """Test when both values are the same."""
        result = last_value("same", "same")
        assert result == "same"

    def test_last_value_different_types(self) -> None:
        """Test with different types."""
        result = last_value("string", 42)
        assert result == 42

    def test_last_value_complex_objects(self) -> None:
        """Test with complex objects."""
        old = {"key": "old"}
        new = {"key": "new"}
        result = last_value(old, new)
        assert result == new
        assert result is new  # Should be the same object reference


class TestBaseState:
    """Test suite for BaseState TypedDict."""

    def test_basestate_can_be_extended(self) -> None:
        """Test that BaseState can be extended with custom fields."""

        class MyState(BaseState):
            custom_field: str
            items: Annotated[list[str], append_list]

        # Should be able to create an instance
        state: MyState = {
            "execution_id": "test-123",
            "input_data": {},
            "output_data": {},
            "current_step": "start",
            "errors": [],
            "custom_field": "value",
            "items": ["item1"],
        }

        assert state["execution_id"] == "test-123"
        assert state["custom_field"] == "value"

    def test_basestate_optional_fields(self) -> None:
        """Test that BaseState fields are optional (total=False)."""
        # With total=False, we can create partial states
        state: BaseState = {
            "execution_id": "test-123",
        }
        assert state["execution_id"] == "test-123"

    def test_basestate_with_all_fields(self) -> None:
        """Test BaseState with all standard fields."""
        state: BaseState = {
            "execution_id": "exec-123",
            "input_data": {"query": "test"},
            "output_data": {
                "_version": 1,
                "_blocks": [
                    {
                        "type": "text",
                        "label": "Result",
                        "content": "success",
                        "format": "plain",
                    }
                ],
            },
            "current_step": "processing",
            "errors": [{"error": "warning", "code": "W001"}],
        }

        assert state["execution_id"] == "exec-123"
        assert state["input_data"]["query"] == "test"
        assert state["output_data"]["_blocks"][0]["content"] == "success"
        assert state["current_step"] == "processing"
        assert len(state["errors"]) == 1

    def test_basestate_errors_structure(self) -> None:
        """Test that errors list accepts dict entries."""
        state: BaseState = {
            "errors": [
                {"error": "Error 1", "step": "step1"},
                {"error": "Error 2", "step": "step2", "recoverable": True},
            ],
        }

        assert len(state["errors"]) == 2
        assert state["errors"][0]["error"] == "Error 1"
        assert state["errors"][1]["recoverable"] is True


class TestReducerIntegration:
    """Test reducers work correctly with state updates."""

    def test_output_data_accumulation(self) -> None:
        """Test that output_data accumulates via merge_dict."""
        initial: dict[str, Any] = {"partial": "result1"}
        update: dict[str, Any] = {"additional": "result2"}

        # Simulate what LangGraph would do with the reducer
        merged = merge_dict(initial, update)
        assert merged == {"partial": "result1", "additional": "result2"}

    def test_errors_accumulation(self) -> None:
        """Test that errors accumulate via append_list."""
        initial: list[dict[str, Any]] = [{"error": "first"}]
        update: list[dict[str, Any]] = [{"error": "second"}]

        # Simulate what LangGraph would do with the reducer
        appended = append_list(initial, update)
        assert len(appended) == 2
        assert appended[0]["error"] == "first"
        assert appended[1]["error"] == "second"

    def test_current_step_update(self) -> None:
        """Test that current_step updates via last_value."""
        steps = ["start", "process", "validate", "finish"]

        current = steps[0]
        for step in steps[1:]:
            current = last_value(current, step)

        assert current == "finish"


class TestStateWithAnnotations:
    """Test that Annotated types work correctly with reducers."""

    def test_custom_state_with_annotated_fields(self) -> None:
        """Test creating a custom state with annotated reducer fields."""

        class ProcessingState(BaseState):
            processed_items: Annotated[list[str], append_list]
            metadata: Annotated[dict[str, Any], merge_dict]
            last_processed: Annotated[str, last_value]

        # Create initial state
        state: ProcessingState = {
            "execution_id": "exec-1",
            "processed_items": ["item1"],
            "metadata": {"source": "api"},
            "last_processed": "item1",
        }

        # Simulate updates that would use reducers
        new_items = append_list(state["processed_items"], ["item2", "item3"])
        new_metadata = merge_dict(state["metadata"], {"count": 3})
        new_last = last_value(state["last_processed"], "item3")

        assert new_items == ["item1", "item2", "item3"]
        assert new_metadata == {"source": "api", "count": 3}
        assert new_last == "item3"

    def test_empty_initial_state_accumulation(self) -> None:
        """Test accumulation starting from empty collections."""
        # Start with empty collections
        output_data: dict[str, Any] = {}
        errors: list[dict[str, Any]] = []

        # First update
        output_data = merge_dict(output_data, {"step1": "done"})
        errors = append_list(errors, [{"step": "step1", "level": "info"}])

        assert output_data == {"step1": "done"}
        assert len(errors) == 1

        # Second update
        output_data = merge_dict(output_data, {"step2": "done"})
        errors = append_list(errors, [{"step": "step2", "level": "warning"}])

        assert output_data == {"step1": "done", "step2": "done"}
        assert len(errors) == 2
