import pytest
from unittest.mock import AsyncMock, patch, MagicMock
from sync_state_machine.sync_system.config import UpdateDirection
from sync_state_machine.sync_system.strategy_ops.update_ops import run_update
from sync_state_machine.domain.construction.sync_strategy import ConstructionTaskSyncStrategy

@pytest.mark.asyncio
async def test_run_update_with_direction_overrides():
    class FakeNode:
        def __init__(self, data):
            self._data = data

        def get_data(self):
            return self._data

    # Setup mock strategy
    strategy = MagicMock()
    strategy.node_type = "TestNode"
    strategy.config.field_direction_key = "sync_direction"
    strategy.config.field_direction_overrides = {
        "local_only": UpdateDirection.PUSH,
        "remote_only": UpdateDirection.PULL,
        "ignore": UpdateDirection.BIDIRECTIONAL # Just to check filtering
    }
    strategy.config.update_direction = UpdateDirection.PUSH
    strategy.local_collection = MagicMock()
    strategy.remote_collection = MagicMock()

    with patch('sync_state_machine.sync_system.strategy_ops.update_ops.add_update_action', new_callable=AsyncMock) as mock_add_action:
        mock_add_action.return_value = []
        
        await run_update(strategy)
        
        # In the active directions, PUSH and PULL should be handled because of overrides.
        # Check PUSH call
        assert mock_add_action.call_count == 2
        
        push_call = mock_add_action.call_args_list[0]
        assert push_call.kwargs['source_is_local'] is True
        assert push_call.kwargs['from_collection'] == strategy.local_collection
        
        pull_call = mock_add_action.call_args_list[1]
        assert pull_call.kwargs['source_is_local'] is False
        assert pull_call.kwargs['from_collection'] == strategy.remote_collection
        
        # Test the node filters
        push_filter = push_call.kwargs['node_filter']
        pull_filter = pull_call.kwargs['node_filter']

        # Node that has 'local_only' -> PUSH
        node_push = FakeNode({"sync_direction": "local_only"})
        assert push_filter(node_push) is True
        assert pull_filter(node_push) is False

        # Node that has 'remote_only' -> PULL
        node_pull = FakeNode({"sync_direction": "remote_only"})
        assert push_filter(node_pull) is False
        assert pull_filter(node_pull) is True

        # Node with unknown key -> default direction (PUSH)
        node_default = FakeNode({"sync_direction": "unknown"})
        assert push_filter(node_default) is True
        assert pull_filter(node_default) is False

        # Node with empty data -> default direction (PUSH)
        node_empty = FakeNode(None)
        assert push_filter(node_empty) is True
        assert pull_filter(node_empty) is False


def test_construction_task_compare_payload_ignores_plan_node_is_audit():
    strategy = ConstructionTaskSyncStrategy(
        "construction_task",
        MagicMock(),
        MagicMock(),
        MagicMock(),
    )

    local_payload = {
        "plan_node": [{"name": "A", "is_audit": True}],
        "task_type": "demo",
    }
    remote_payload = {
        "plan_node": [{"name": "A", "is_audit": False}],
        "task_type": "demo",
    }

    assert strategy.normalize_compare_payload(local_payload) == strategy.normalize_compare_payload(remote_payload)
