from unittest.mock import MagicMock, patch

import pytest

from GramAddict.core.dm_engine import _run_zero_latency_dm_loop
from GramAddict.core.session_state import SessionState


@pytest.fixture
def mock_device():
    device = MagicMock()
    # Initial inbox state
    device.dump_hierarchy.return_value = "<xml><node text='Inbox'/></xml>"
    return device


@pytest.fixture
def mock_cognitive_stack():
    telepathic = MagicMock()
    dopamine = MagicMock()
    dopamine.is_app_session_over.return_value = False
    dopamine.wants_to_change_feed.side_effect = [False, True]
    dopamine.boredom = 0

    dm_memory = MagicMock()

    resonance = MagicMock()
    resonance.persona_prompt = "You are a friendly bot."
    resonance.args.ai_model = "test-model"

    return {"telepathic": telepathic, "dopamine": dopamine, "dm_memory": dm_memory, "resonance": resonance}


def test_e2e_dm_full_flow_success(mock_device, mock_cognitive_stack):
    """
    E2E scenario:
    1. Found 1 unread message.
    2. Opened chat.
    3. Read context.
    4. Generated response.
    5. Sent message.
    6. Guarded back-navigation (keyboard closed + activity exit).
    """
    telepathic = mock_cognitive_stack["telepathic"]

    hierarchy_items = [
        "<xml>Inbox with unread</xml>",  # Loop 1 start
        "<xml>Thread view</xml>",  # Context read
        "<xml>Thread view</xml>",  # Input field find
        "<xml>Thread view</xml>",  # Send button find
        "<xml><node resource-id='com.instagram.android:id/direct_thread_header'/></xml>",  # Navigation check AFTER back
        "<xml>Inbox View</xml>",  # Loop 2 start (exit)
        "<xml>Inbox View</xml>",  # Buffer
    ]
    hierarchy_iterator = iter(hierarchy_items)
    mock_device.dump_hierarchy.side_effect = lambda: next(hierarchy_iterator)

    # Semantic node responses
    telepathic._extract_semantic_nodes.side_effect = [
        [{"x": 100, "y": 100, "text": "New Message"}],  # unread_threads
        [{"text": "Hello there!"}],  # msg_nodes (context)
        [{"x": 200, "y": 200}],  # input_nodes
        [{"x": 300, "y": 300}],  # send_nodes
        [],  # Loop 2: no unread
        [],  # Buffer
    ]

    mock_cognitive_stack["dopamine"].boredom = 0
    mock_cognitive_stack["dopamine"].wants_to_change_feed.side_effect = [False, True, True]

    session_state = MagicMock(spec=SessionState)
    session_state.check_limit.return_value = False
    session_state.totalMessages = 0

    mock_configs = MagicMock()
    mock_configs.args.disable_ai_messaging = False
    mock_configs.args.ai_condenser_model = "test-model"
    mock_configs.args.ai_condenser_url = "http://localhost:11434/api/generate"

    with (
        patch("GramAddict.core.llm_provider.query_llm", return_value={"response": "Hi! How can I help?"}),
        patch("GramAddict.core.bot_flow._humanized_click"),
        patch("GramAddict.core.bot_flow.sleep"),
        patch("GramAddict.core.stealth_typing.ghost_type"),
    ):
        result = _run_zero_latency_dm_loop(
            mock_device, MagicMock(), MagicMock(), mock_configs, session_state, "target", mock_cognitive_stack
        )

    assert result == "BOREDOM_CHANGE_FEED"
    # Ensure navigation at least attempted to exit
    assert mock_device.press.call_count >= 2
    mock_device.press.assert_called_with("back")


def test_e2e_dm_no_messages(mock_device, mock_cognitive_stack):
    """
    E2E scenario: No messages found, exit immediately.
    """
    telepathic = mock_cognitive_stack["telepathic"]
    mock_cognitive_stack["dopamine"].wants_to_change_feed.return_value = True

    telepathic._extract_semantic_nodes.return_value = []  # No unreads

    session_state = MagicMock(spec=SessionState)
    session_state.check_limit.return_value = False

    result = _run_zero_latency_dm_loop(
        mock_device, MagicMock(), MagicMock(), MagicMock(), session_state, "target", mock_cognitive_stack
    )

    assert result == "BOREDOM_CHANGE_FEED"
    # Should only press back once to exit Inbox
    assert mock_device.press.call_count == 1
