from unittest.mock import MagicMock, patch

import pytest

from GramAddict.core.dm_engine import _run_zero_latency_dm_loop


@pytest.fixture
def dm_mock_dependencies():
    device = MagicMock()
    zero_engine = MagicMock()
    nav_graph = MagicMock()

    class ConfigArgs:
        disable_ai_messaging = False

    configs = MagicMock()
    configs.args = ConfigArgs()

    session_state = MagicMock()
    session_state.check_limit.return_value = (False, False, False, False)
    session_state.totalMessages = 0

    telepathic = MagicMock()
    dopamine = MagicMock()
    dopamine.is_app_session_over.side_effect = [False, False, True, True]
    dopamine.wants_to_change_feed.return_value = False
    dopamine.boredom = 0.0

    crm = MagicMock()

    cognitive_stack = {
        "telepathic": telepathic,
        "dopamine": dopamine,
        "crm": crm,
    }

    return device, zero_engine, nav_graph, configs, session_state, cognitive_stack


def test_dm_engine_basic_loop(dm_mock_dependencies):
    device, zero_engine, nav_graph, configs, session_state, cognitive_stack = dm_mock_dependencies

    telepathic = cognitive_stack["telepathic"]
    crm = cognitive_stack["crm"]

    # Simulate Telepathic node extraction for the DM flow
    telepathic._extract_semantic_nodes.side_effect = [
        [{"x": 100, "y": 200, "skip": False}],  # Call 1: Unread thread
        [{"x": 10, "y": 20, "skip": False, "text": "hey how are you?"}],  # Call 2: Context
        [{"x": 150, "y": 250, "skip": False}],  # Call 3: Input field
        [{"x": 200, "y": 250, "skip": False}],  # Call 4: Send button
        [],  # Call 5: Iteration 2 (No more unreads)
        [],  # Call 6: Safety
        [],  # Call 7: Safety
    ]

    with (
        patch("GramAddict.core.bot_flow.sleep"),
        patch("GramAddict.core.bot_flow._humanized_click") as mock_click,
        patch("GramAddict.core.stealth_typing.ghost_type", autospec=True) as mock_ghost_type,
        patch("GramAddict.core.llm_provider.query_llm", return_value={"response": "I am good, thanks!"}),
    ):
        res = _run_zero_latency_dm_loop(
            device, zero_engine, nav_graph, configs, session_state, "MessageInbox", cognitive_stack
        )

        # Clicked thread -> Clicked input field -> Clicked send
        assert mock_click.call_count == 3

        mock_ghost_type.assert_called_with(device, "I am good, thanks!", speed="fast")

        # Verify persistence memory is triggered
        crm.log_sent_dm.assert_called_with("unknown_target", "I am good, thanks!", "", [])

        assert session_state.totalMessages == 1
        assert res == "SESSION_OVER" or res == "BOREDOM_CHANGE_FEED"


def test_dm_engine_no_unread(dm_mock_dependencies):
    device, zero_engine, nav_graph, configs, session_state, cognitive_stack = dm_mock_dependencies

    telepathic = cognitive_stack["telepathic"]
    dopamine = cognitive_stack["dopamine"]

    # Telepathic finds no unread threads
    telepathic._extract_semantic_nodes.return_value = []

    with patch("GramAddict.core.bot_flow.sleep"):
        res = _run_zero_latency_dm_loop(
            device, zero_engine, nav_graph, configs, session_state, "MessageInbox", cognitive_stack
        )

        # Boredom should jump by 50.0 immediately because inbox is empty
        assert dopamine.boredom >= 50.0
        assert res == "BOREDOM_CHANGE_FEED"
