from unittest.mock import MagicMock, patch

import pytest

from GramAddict.core.device_facade import create_device, get_device_info


@pytest.fixture
def mock_u2():
    with patch("uiautomator2.connect") as mock_connect:
        mock_device = MagicMock()
        mock_connect.return_value = mock_device
        yield mock_connect, mock_device


def test_create_device_success(mock_u2):
    mock_connect, mock_device = mock_u2
    facade = create_device("fake_id", "com.instagram.android")
    assert facade is not None
    assert facade.device_id == "fake_id"
    assert facade.app_id == "com.instagram.android"


def test_create_device_exception(mock_u2):
    mock_connect, mock_device = mock_u2
    mock_connect.side_effect = Exception("Fatal boot error")
    with pytest.raises(Exception, match="Fatal boot error"):
        create_device("fake_id", "com.instagram.android")


def test_get_device_info(mock_u2):
    mock_connect, mock_device = mock_u2
    mock_device.info = {"productName": "Galaxy", "sdkInt": 33}

    facade = create_device("fake_id", "app")
    assert facade.get_info() == {"productName": "Galaxy", "sdkInt": 33}

    # Test global helper
    get_device_info(facade)  # Should not crash
    get_device_info(None)  # Should not crash


def test_cm_to_pixels(mock_u2):
    mock_connect, mock_device = mock_u2
    mock_device.info = {"displaySizeDpX": 400, "displayWidth": 1080}
    facade = create_device("fake_id", "app")

    pixels = facade.cm_to_pixels(5.0)
    assert isinstance(pixels, int)
    # The pure calculation logic ensures it returns an int > 0
    assert pixels > 0


def test_wake_up(mock_u2):
    mock_connect, mock_device = mock_u2
    facade = create_device("fake_id", "app")

    # Screen off
    mock_device.info = {"screenOn": False}
    with patch("GramAddict.core.device_facade.sleep"):
        facade.wake_up()
    mock_device.screen_on.assert_called_once()
    mock_device.press.assert_called_with("home")

    # Screen on (should do nothing)
    mock_device.reset_mock()
    mock_device.info = {"screenOn": True}
    facade.wake_up()
    mock_device.screen_on.assert_not_called()


def test_press(mock_u2):
    mock_connect, mock_device = mock_u2
    facade = create_device("fake_id", "app")
    facade.press("back")
    mock_device.press.assert_called_with("back")


def test_click_and_human_click(mock_u2):
    mock_connect, mock_device = mock_u2
    facade = create_device("fake_id", "app")

    from GramAddict.core.physics.biomechanics import PhysicsBody
    from GramAddict.core.physics.sendevent_injector import SendEventInjector

    PhysicsBody.reset()
    SendEventInjector.reset()

    with patch("GramAddict.core.device_facade.sleep"):
        with patch("GramAddict.core.device_facade.SendEventInjector") as MockInjector:
            mock_inj = MagicMock()
            MockInjector.get_instance.return_value = mock_inj

            # Click dict directly (safe coordinates)
            facade.click(obj={"x": 500, "y": 1000})
            mock_inj.inject_gesture.assert_called()

            # Click obj with bounds (safe coordinates)
            mock_inj.reset_mock()
            obj = MagicMock()
            obj.bounds.return_value = (400, 900, 600, 1100)
            facade.click(obj=obj)
            mock_inj.inject_gesture.assert_called()

            # Click bounds failure fallback
            mock_device.reset_mock()
            obj2 = MagicMock()
            obj2.bounds.side_effect = Exception("No bounds")
            facade.click(obj=obj2)
            obj2.click.assert_called()

            # Click x,y with edge coordinates triggers guard (direct shell tap)
            mock_device.reset_mock()
            facade.human_click(10, 10)
            mock_device.shell.assert_called_with("input tap 10 10")


def test_swipes(mock_u2):
    mock_connect, mock_device = mock_u2
    facade = create_device("fake_id", "app")

    facade.swipe_points(0, 0, 100, 100, 0.5)
    mock_device.shell.assert_called_with("input swipe 0 0 100 100 500")

    mock_device.reset_mock()
    facade.human_swipe(0, 0, 100, 100, 0.5)
    mock_device.shell.assert_called_with("input swipe 0 0 100 100 500")


def test_get_current_app(mock_u2):
    mock_connect, mock_device = mock_u2
    mock_device.app_current.return_value = {"package": "com.target"}
    facade = create_device("fake_id", "app")

    assert facade._get_current_app() == "com.target"


def test_find_and_dump_and_screenshot(mock_u2):
    mock_connect, mock_device = mock_u2
    facade = create_device("fake_id", "app")

    mock_device.return_value = "ui_node"
    assert facade.find(text="hello") == "ui_node"

    mock_device.dump_hierarchy.return_value = "<xml></xml>"
    assert facade.dump_hierarchy() == "<xml></xml>"

    img_mock = MagicMock()
    mock_device.screenshot.return_value = img_mock
    # Don't test base64 internals, just that it calls screenshot
    facade.get_screenshot_b64()
    mock_device.screenshot.assert_called_once()


def test_find_semantic(mock_u2):
    mock_connect, mock_device = mock_u2
    facade = create_device("fake_id", "app")

    with patch("GramAddict.core.telepathic_engine.TelepathicEngine._get_instance", create=True):
        # Instead of _get_instance, patch get_instance which is what the code calls
        with patch("GramAddict.core.telepathic_engine.TelepathicEngine.get_instance") as get_inst:
            engine_mock = MagicMock()
            get_inst.return_value = engine_mock
            engine_mock.find_best_node.return_value = {"x": 1}

            mock_device.dump_hierarchy.return_value = "<xml></xml>"
            res = facade.find_semantic("Hello")
            assert res == {"x": 1}
            engine_mock.find_best_node.assert_called_once()
