from unittest.mock import patch

from GramAddict.core.physics.timing import align_active_post, wait_for_post_loaded, wait_for_story_loaded
from tests.e2e.test_e2e_behaviors import BehaviorSimulator


def test_wait_for_post_detects_feed():
    sim = BehaviorSimulator()
    with open("tests/fixtures/organic_post.xml", "r") as f:
        sim.mock_xml = f.read()

    with patch("GramAddict.core.physics.timing.sleep", autospec=True):
        assert wait_for_post_loaded(sim, timeout=1) is True


def test_wait_for_post_timeout_and_adaptive_snap():
    sim = BehaviorSimulator()
    # Empty XML will cause timeout
    sim.mock_xml = "<hierarchy></hierarchy>"

    with patch("GramAddict.core.physics.timing.sleep", autospec=True):
        assert wait_for_post_loaded(sim, timeout=1) is False

    swipes = [a for a in sim.actions_taken if a[0] == "swipe"]
    assert len(swipes) > 0


def test_wait_for_story_detects_viewer():
    sim = BehaviorSimulator()
    sim.mock_xml = '<node class="hierarchy"><node resource-id="com.instagram.android:id/reel_viewer_root" /></node>'

    with patch("GramAddict.core.physics.timing.sleep", autospec=True):
        assert wait_for_story_loaded(sim, timeout=1) is True


def test_wait_for_story_timeout():
    sim = BehaviorSimulator()
    sim.mock_xml = "<hierarchy></hierarchy>"

    with patch("GramAddict.core.physics.timing.sleep", autospec=True):
        assert wait_for_story_loaded(sim, timeout=1) is False


def test_align_active_post_centers_content():
    sim = BehaviorSimulator()

    # We load real organic post
    with open("tests/fixtures/organic_post.xml", "r") as f:
        # We simulate the header being at bounds [0, 800][1080, 950] instead of [0, 200][1080, 350]
        # This will make the diff > 100
        # The node in organic_post.xml is:
        # resource-id="com.instagram.android:id/row_feed_profile_header" bounds="[0,665][1080,802]"
        # center Y = 733. Target is 250. Diff = 483.
        sim.mock_xml = f.read()

    def mock_swipe(sx, sy, ex, ey, duration=None):
        sim.actions_taken.append(("swipe", sx, sy, ex, ey))
        # Simulate that the swipe successfully aligned it
        # Move both the header and the name node
        sim.mock_xml = sim.mock_xml.replace('bounds="[0,665][1080,802]"', 'bounds="[0,200][1080,337]"')
        sim.mock_xml = sim.mock_xml.replace('bounds="[128,665][768,731]"', 'bounds="[128,200][768,266]"')

    sim.swipe = mock_swipe

    with patch("GramAddict.core.physics.timing.sleep", autospec=True):
        try:
            aligned = align_active_post(sim)
        except Exception as e:
            print(f"Exception: {e}")
            raise

    assert aligned is True


def test_align_active_post_already_centered():
    sim = BehaviorSimulator()

    with open("tests/fixtures/organic_post.xml", "r") as f:
        # Move the header to target Y = 250 -> bounds [0,180][1080,320]
        xml_str = f.read()
        xml_str = xml_str.replace('bounds="[0,665][1080,802]"', 'bounds="[0,180][1080,320]"')
        xml_str = xml_str.replace('bounds="[128,665][768,731]"', 'bounds="[128,180][768,246]"')
        sim.mock_xml = xml_str

    with patch("GramAddict.core.physics.timing.sleep", autospec=True):
        aligned = align_active_post(sim)

    # It considers it already aligned
    assert aligned is True

    swipes = [a for a in sim.actions_taken if a[0] == "swipe"]
    assert len(swipes) == 0


def test_align_post_with_no_header():
    sim = BehaviorSimulator()
    sim.mock_xml = "<hierarchy></hierarchy>"

    with patch("GramAddict.core.physics.timing.sleep", autospec=True):
        aligned = align_active_post(sim)

    assert aligned is False
