"""
GoalExecutor.achieve() E2E Integration Test
=============================================

This is the MOST CRITICAL missing test in the entire suite.

GoalExecutor.achieve() is the central autonomous brain — called in EVERY
bot session via bot_flow.py. Until now, it had ZERO E2E coverage.

The deleted test_e2e_autonomous_session.py was a lying mock that never
called achieve() at all. The production bug it hid (GoalExecutor instantiated
with wrong args → AttributeError) survived for weeks undetected.

These tests use REAL XML fixtures, real ScreenIdentity, real GoalPlanner,
real ScreenTopology, and real PathMemory. The only thing mocked is the
uiautomator2 device connection (via make_real_device_with_image).

Test Strategy:
1. Provide a sequence of XML dumps simulating screen transitions
2. Call achieve() with a goal the HD Map knows how to route
3. Verify achieve() returns True/False based on structural reality
"""

import os

FIXTURE_DIR = os.path.join(os.path.dirname(__file__), "fixtures")


def _load_fixture(name: str) -> str:
    path = os.path.join(FIXTURE_DIR, name)
    with open(path, "r", encoding="utf-8") as f:
        return f.read()


class TestGoalExecutorAchieveNavigation:
    """Tests GoalExecutor.achieve() with real XML fixture sequences."""

    def test_achieve_navigates_home_to_explore(self, make_real_device_with_image):
        """
        Goal: 'open explore feed' starting from HOME_FEED.

        Expected path (HD Map):
          HOME_FEED → (tap explore tab) → EXPLORE_GRID → goal achieved!

        dump_hierarchy call sequence:
          1. perceive() → home_feed (initial state)
          2. _execute_action('tap explore tab') → dump for find_best_node
          3. _execute_action verification → explore_grid (post-click)
          4. perceive() on next iteration → explore_grid (goal check)
          5. _is_goal_achieved returns True → achieve() returns True

        If GOAP can't route this, the entire bot is broken.
        """
        from GramAddict.core.goap import GoalExecutor

        home_xml = _load_fixture("home_feed_real.xml")
        explore_xml = _load_fixture("explore_grid_real.xml")

        # Sequence: perceive → find_node → verify → perceive (goal check)
        xml_sequence = [
            home_xml,  # 1. perceive(): identify HOME_FEED
            home_xml,  # 2. _execute_action: dump for find_best_node
            explore_xml,  # 3. _execute_action: post-click verify
            explore_xml,  # 4. perceive(): _is_goal_achieved → True
            explore_xml,  # 5. safety buffer
        ]

        device = make_real_device_with_image(None, xml_sequence)

        executor = GoalExecutor(device=device, bot_username="testuser")

        result = executor.achieve("open explore feed", max_steps=5)

        assert result is True, (
            "GoalExecutor failed to navigate from HOME_FEED to EXPLORE_GRID! "
            "This is the most basic navigation the bot must be able to do."
        )

    def test_achieve_recognizes_already_on_target(self, make_real_device_with_image):
        """
        When the bot is ALREADY on the target screen, achieve() must return
        True immediately (0 steps) without trying to navigate.

        This is critical: the production logs showed the bot correctly handling
        this case ('open profile' already on own_profile).
        """
        from GramAddict.core.goap import GoalExecutor

        explore_xml = _load_fixture("explore_grid_real.xml")

        # Only 1 dump needed: perceive → already on EXPLORE_GRID
        xml_sequence = [
            explore_xml,  # perceive(): already on target
            explore_xml,  # safety buffer
        ]

        device = make_real_device_with_image(None, xml_sequence)

        executor = GoalExecutor(device=device, bot_username="testuser")

        result = executor.achieve("open explore feed", max_steps=5)

        assert result is True, (
            "GoalExecutor couldn't recognize it's ALREADY on EXPLORE_GRID! " "This causes unnecessary navigation loops."
        )

    def test_achieve_returns_false_on_max_steps_exhaustion(self, make_real_device_with_image):
        """
        When achieve() exhausts max_steps without reaching the goal,
        it MUST return False — not hang, not crash, not return None.

        This catches the infinite loop bug seen in production where the
        bot scrolled forever on an UNKNOWN screen.
        """
        from GramAddict.core.goap import GoalExecutor

        home_xml = _load_fixture("home_feed_real.xml")

        # Provide only HOME_FEED dumps. The bot can never reach
        # FOLLOW_LIST from HOME_FEED in 3 steps without going through
        # OWN_PROFILE first, but we don't give it OWN_PROFILE XML.
        xml_sequence = [home_xml] * 20  # All dumps return HOME_FEED

        device = make_real_device_with_image(None, xml_sequence)

        executor = GoalExecutor(device=device, bot_username="testuser")

        result = executor.achieve("open following list", max_steps=3)

        assert result is False, (
            "GoalExecutor did not return False after exhausting max_steps! "
            "This means the bot could loop forever in production."
        )

    def test_achieve_return_type_is_bool(self, make_real_device_with_image):
        """
        Regression test for the critical bot_flow.py lie:
        achieve() was compared to 'GOAL_ACHIEVED' (string) instead of True.

        This test guarantees the return type contract is enforced.
        """
        from GramAddict.core.goap import GoalExecutor

        explore_xml = _load_fixture("explore_grid_real.xml")

        device = make_real_device_with_image(None, [explore_xml] * 3)

        executor = GoalExecutor(device=device, bot_username="testuser")

        result = executor.achieve("open explore feed", max_steps=5)

        assert isinstance(result, bool), (
            f"achieve() returned {type(result).__name__} instead of bool! "
            f"Value: {result!r}. This breaks the bot_flow.py success check."
        )
