from unittest.mock import MagicMock, patch

import pytest


@pytest.fixture
def mock_device():
    device = MagicMock()
    # Initial screen: Home
    device.dump_hierarchy.side_effect = [
        "<home_xml/>",  # Initial perceive
        "<messages_xml/>",  # After click
        "<messages_xml/>",  # Final check
    ]
    device.app_id = "com.instagram.android"
    return device


@pytest.fixture
def mock_nav_db(monkeypatch):
    """
    Bulletproof mock for Qdrant isolation.
    """
    storage = {}  # collection -> seed -> payload

    class MockDB:
        def __init__(self, collection_name, **kwargs):
            self.collection_name = collection_name
            self.is_connected = True
            self._storage = storage

        def _get_embedding(self, text):
            return [0.1] * 768

        def upsert_point(self, seed, payload, **kwargs):
            if self.collection_name not in self._storage:
                self._storage[self.collection_name] = {}
            self._storage[self.collection_name][seed] = payload
            return True

        @property
        def client(self):
            client_mock = MagicMock()

            def mock_scroll(collection_name, **kwargs):
                mock_points = []
                coll_data = self._storage.get(collection_name, {})
                for payload in coll_data.values():
                    p = MagicMock()
                    p.payload = payload
                    mock_points.append(p)
                return (mock_points, None)

            client_mock.scroll.side_effect = mock_scroll
            client_mock.delete_collection.side_effect = lambda c: self._storage.pop(c, None)
            return client_mock

    import GramAddict.core.goap

    monkeypatch.setattr(GramAddict.core.goap, "QdrantBase", MockDB)
    yield storage


def test_dynamic_discovery_learning(device, mock_nav_db):
    """
    TDD: Start blank, achieve a goal, verify knowledge is gained.
    """
    from GramAddict.core.goap import GoalExecutor, ScreenType

    username = "test_discovery_user"
    # We need to mock TelepathicEngine.get_instance to avoid it failing in execute_action
    with patch("GramAddict.core.telepathic_engine.TelepathicEngine.get_instance") as mock_te:
        mock_te.return_value.verify_success.return_value = True
        mock_te.return_value.find_best_node.return_value = {"x": 100, "y": 200}

        executor = GoalExecutor(device, username)
        executor.planner.knowledge.wipe()  # Start clean

        # 1. Execute 'open messages'
        # We mock perceive to return HOME then DM_INBOX
        with patch.object(executor, "perceive") as mock_perceive:
            mock_perceive.side_effect = [
                {"screen_type": ScreenType.HOME_FEED, "available_actions": ["tap messages tab"]},
                {"screen_type": ScreenType.DM_INBOX, "available_actions": []},
                {"screen_type": ScreenType.DM_INBOX, "available_actions": []},
            ]

            # Using real achieve/execute logic
            success = executor.achieve("open messages")
            assert success is True

        # 2. Verify knowledge was LEARNED automatically
        reqs = executor.planner.knowledge.get_requirements("open messages")
        assert ScreenType.DM_INBOX in reqs


def test_tab_mapping_learning(device, mock_nav_db):
    """Verify that tapping a tab records its destination."""
    from GramAddict.core.goap import GoalExecutor, ScreenType

    username = "test_tab_user"
    executor = GoalExecutor(device, username)
    executor.planner.knowledge.wipe()

    # Tapping 'reels tab' should land on REELS_FEED
    executor.planner.knowledge.learn_screen_mapping("clips_tab", ScreenType.REELS_FEED)

    tab = executor.planner.knowledge.get_action_for_screen(ScreenType.REELS_FEED)
    assert tab == "clips_tab"
