import logging
import random
import time

from GramAddict.core.compiler_engine import VLMCompilerEngine
from GramAddict.core.goap import GoalExecutor, ScreenType
from GramAddict.core.qdrant_memory import NavigationMemoryDB
from GramAddict.core.screen_topology import ScreenTopology
from GramAddict.core.situational_awareness import SituationalAwarenessEngine, SituationType
from GramAddict.core.utils import random_sleep

logger = logging.getLogger(__name__)


class Node:
    def __init__(self, name: str):
        self.name = name
        self.transitions = {}  # Action (e.g. "tap_search") -> Node


class QNavGraph:
    """
    Topological Navigation Map
    Maintains a directed graph of UI states. Instead of hardcoded navigation scripts,
    the bot traverses this graph. If a path fails, it invokes the VLMCompilerEngine to repair it.
    """

    def __init__(self, device):
        self.device = device
        self.nodes = {}
        self.current_state = "UNKNOWN"
        self.nav_memory = NavigationMemoryDB()
        self.sae = SituationalAwarenessEngine.get_instance(device)
        self.goap = GoalExecutor.get_instance(device)

        self.compiler = VLMCompilerEngine(device)
        self._load_graph()

    def _load_graph(self):
        """Loads the topological map from Qdrant. Merges with core seeds from ScreenTopology (SSOT)."""
        logger.debug("🌐 [NavGraph] Syncing topological map with Qdrant...")
        self.nodes = self.nav_memory.get_all_transitions()

        # Generate core_nodes from ScreenTopology (single source of truth)
        core_nodes = {}
        for screen_type, transitions in ScreenTopology.TRANSITIONS.items():
            # Reverse lookup: ScreenType → QNavGraph string name from SSOT
            screen_name_map = {
                v: k
                for k, v in ScreenTopology.SCREEN_NAME_MAP.items()
                if v not in (ScreenType.HOME_FEED, ScreenType.EXPLORE_GRID) or k not in ("StoriesFeed", "SearchFeed")
            }
            node_name = screen_name_map.get(screen_type)
            if not node_name:
                continue
            node_transitions = {}
            for action, target_screen in transitions.items():
                # Convert action format: "tap profile tab" → "tap_profile_tab"
                action_key = action.replace(" ", "_")
                target_name = screen_name_map.get(target_screen, target_screen.name)
                node_transitions[action_key] = target_name
            core_nodes[node_name] = {"transitions": node_transitions}

        # Merge core nodes into loaded nodes
        for node, data in core_nodes.items():
            if node not in self.nodes:
                self.nodes[node] = {"transitions": {}}
            for action, target in data["transitions"].items():
                if action not in self.nodes[node]["transitions"]:
                    self.nodes[node]["transitions"][action] = target
                    self.nav_memory.store_transition(node, action, target)

    def _save_graph(self):
        """Deprecated: Navigation state is now persisted per-transition in Qdrant."""
        pass

    def navigate_to(self, target_state: str, zero_engine, recovery_attempts: int = 0):
        """
        GOAP-powered autonomous navigation.
        Delegates to the Goal-Oriented Action Planner instead of
        using hardcoded state machines and BFS pathfinding.
        """
        logger.info(f"📍 [GOAP] Navigating autonomously to: {target_state}")

        # Set bot username for screen identity
        try:
            from GramAddict.core.config import Config

            args = getattr(Config(), "args", None)
            if args and hasattr(args, "username"):
                self.goap.screen_id.bot_username = args.username.lower()
        except Exception as e:
            logger.debug(f"⚠️ [GOAP] Skipping username sync: {e}")

        success = self.goap.navigate_to_screen(target_state)

        if success:
            self.current_state = target_state
            logger.info(f"✅ [GOAP] Reached {target_state}")
        else:
            logger.error(f"❌ [GOAP] Failed to reach {target_state}")
            # Final fallback: force app start and reset
            if recovery_attempts < 2:
                logger.warning(
                    f"🔄 [GOAP Recovery] Step {recovery_attempts + 1}: Attempting app restart to escape softlock..."
                )
                self.device.app_start(self.device.app_id, use_monkey=True)
                random_sleep(3.0, 4.5)
                self.current_state = "HomeFeed"
                # Clear GOAP status for fresh attempt
                return self.navigate_to(target_state, zero_engine, recovery_attempts + 1)
            else:
                logger.critical(
                    f"🛑 [GOAP Recovery] Max recovery attempts reached. Navigation to {target_state} aborted."
                )

        return success

    def do(self, goal: str, **kwargs) -> bool:
        """
        GOAP-powered action execution.
        Replaces _execute_transition() for post interactions.

        Usage:
            nav_graph.do("like this post")       # instead of _execute_transition("tap_like_button")
            nav_graph.do("follow this user")     # instead of _execute_transition("tap_follow_button")
            nav_graph.do("type and post comment", text="Nice!")
        """

        return self.goap._execute_action(goal, **kwargs)

    def _find_path(self, start: str, end: str):
        """Delegates to ScreenTopology for BFS pathfinding (SSOT)."""
        from_screen = ScreenTopology.SCREEN_NAME_MAP.get(start)
        to_screen = ScreenTopology.SCREEN_NAME_MAP.get(end)
        if not from_screen or not to_screen:
            return None

        route = ScreenTopology.find_route(from_screen, to_screen)
        if route is None:
            return None

        # Convert back to QNavGraph action format: "tap profile tab" → "tap_profile_tab"
        return [action.replace(" ", "_") for action, _ in route]

    def _clear_anomaly_obstacles(self, max_attempts=2, xml_dump: str = None) -> bool:
        """
        Delegates ALL obstacle detection to the Situational Awareness Engine.
        Returns True if an obstacle was cleared, False otherwise.
        """
        success = self.sae.ensure_clear_screen(max_attempts=max_attempts + 5, initial_xml=xml_dump)
        return success

    def _execute_transition(self, action: str, max_retries: int = 2) -> bool:
        """
        Executes a transition (e.g. 'tap_explore_tab') using the Telepathic Semantic Engine.
        """
        from GramAddict.core.telepathic_engine import TelepathicEngine

        engine = TelepathicEngine.get_instance()

        failed_positions = set()  # Track (x, y) of clicks that failed, for grid retry diversity

        for attempt in range(max_retries + 1):
            context_xml = self.device.dump_hierarchy()

            # ── Z-Depth Guard / Anomaly Obstacle Clearance ──
            cleared_something = self._clear_anomaly_obstacles(xml_dump=context_xml)
            if cleared_something:
                # Re-acquire context after clearing obstacle
                context_xml = self.device.dump_hierarchy()

            # We phrase the action as an intent for the semantic engine
            # e.g. "tap_explore_tab" -> "tap explore tab"
            # We add some common synonyms for Instagram to help the vector engine
            intent_map = {
                # Navigation (Bottom Bar) — aligned with fast-path keys
                "tap_home_tab": "tap home tab",
                "tap_explore_tab": "tap explore tab",
                "tap_profile_tab": "tap profile tab",
                "tap_reels_tab": "tap reels tab",
                "tap_create_tab": "tap create post tab",
                # Post Interaction — aligned with fast-path keys
                "tap_like_button": "tap like button",
                "tap_comment_button": "tap comment button",
                "tap_post_username": "tap post username",
                "tap_share_button": "tap share button",
                "tap_save_button": "tap save button",
                # Grid & Profile
                "tap_explore_grid_item": "first image in explore grid",
                "tap_story_tray_item": "profile picture avatar story ring",
                "tap_follow_button": "tap 'Follow' button on profile",
                "tap_grid_first_post": "first image post in profile grid",
                "tap_back": "tap back button icon arrow",
                "tap_message_icon": "tap direct message icon inbox",
                "tap_newsfeed_tab": "tap activity heart icon notifications",
            }
            intent_description = intent_map.get(action, action.replace("_", " "))

            # Use TelepathicEngine to find the most likely node for this intent
            # If vector score < 0.82, it will trigger the Vision Cortex Fallback (VLM)
            # Pass failed_positions so grid fast-path picks a different item on retry
            best_node = engine.find_best_node(
                context_xml,
                intent_description,
                min_confidence=0.82,
                device=self.device,
                skip_positions=failed_positions,
            )

            # ── Blocked by Modal Recovery ──
            if best_node and best_node.get("blocked_by_modal"):
                logger.warning(
                    f"🛡️ [Modal Recovery] Navigation '{action}' is blocked by a modal. Attempting anomaly clearance..."
                )
                self._clear_anomaly_obstacles()
                if attempt < max_retries:
                    context_xml = self.device.dump_hierarchy()
                    continue
                else:
                    logger.error(
                        f"❌ [Modal Recovery] Persistent blockage for '{action}'. Escalating to Context Lost (App Restart)."
                    )
                    return "CONTEXT_LOST"

            if not best_node:
                logger.debug(f"_execute_transition: TelepathicEngine found no matching node for '{action}'")
                # Check if we are even in the right app
                current_app = self.device._get_current_app()
                if current_app != self.device.app_id:
                    logger.warning(
                        f"⚠️ [Context Lost] Currently in '{current_app}', expected '{self.device.app_id}'. Transition '{action}' aborted."
                    )
                    return "CONTEXT_LOST"

                # Try again if within retries, UI might be animating
                if attempt < max_retries:
                    time.sleep(1.0)
                    continue

                # FINAL ATTEMPT ESCAPE:
                # If we are looking for the 'Home' tab (our baseline) and everything failed,
                # we might be in an unknown sub-view. Try one last 'BACK' press.
                if action == "tap_home_tab":
                    logger.warning(
                        "📍 [Escape] Home tab not found after all retries. Attempting final BACK press to escape sub-view..."
                    )
                    self.device.press("back")
                    time.sleep(2.0)

                return False

            if best_node.get("skip") or (best_node.get("selected") and "tab" in action):
                logger.info(
                    f"⏭️ Skipping physical tap for '{action}' (Semantic Fast-Path indicated state already fulfilled)"
                )
                return True

            source_tag = best_node.get("source", "telepathic").replace("_", " ").title()
            logger.info(
                f"QNavGraph executing transition '{action}' via [{source_tag}] (Score: {best_node.get('score', 1.0):.3f})"
            )

            # Execute click
            self.device.click(obj=best_node)
            time.sleep(random.uniform(1.6, 2.8))

            # ── Post-Click Verification: Did it work? ──
            post_click_xml = self.device.dump_hierarchy()

            # ── App Perimeter Guard (SAE-powered) ──
            post_situation = self.sae.perceive(post_click_xml)
            if post_situation in (
                SituationType.OBSTACLE_FOREIGN_APP,
                SituationType.OBSTACLE_SYSTEM,
                SituationType.OBSTACLE_MODAL,
            ):
                logger.warning(
                    f"🚨 [SAE Perimeter] Transition '{action}' caused drift ({post_situation.value}). Initiating autonomous recovery..."
                )
                failed_positions.add((best_node["x"], best_node["y"]))
                engine.reject_click(intent_description)

                # Let SAE handle recovery autonomously
                recovered = self.sae.ensure_clear_screen(max_attempts=5)
                if not recovered:
                    return "CONTEXT_LOST"

                # Screen is clear but the transition itself failed — retry
                if attempt < max_retries:
                    logger.info(f"🔄 [SAE Recovery] Screen recovered. Retrying transition '{action}'...")
                    continue
                return "CONTEXT_LOST"

            # 1. Semantic Verification (Hardened)
            is_verified = engine.verify_success(intent_description, post_click_xml)

            # 2. UI Change Verification (Fallback/Navigation)
            ui_changed = post_click_xml != context_xml

            if is_verified and ui_changed:
                engine.confirm_click(intent_description)
                return True
            elif not ui_changed:
                logger.warning(f"⚠️ [Nav] Click on '{action}' did not change UI. Learning from failure.")
                failed_positions.add((best_node["x"], best_node["y"]))
                engine.reject_click(intent_description)
                if attempt < max_retries:
                    logger.info(
                        f"🔄 [Autonomy] UI unchanged. Retrying transition '{action}' ({attempt + 1}/{max_retries})..."
                    )
                    continue
                else:
                    return False
            else:
                # UI changed but semantic verification failed (accidental click or false positive)
                logger.warning(
                    f"❌ [Ambiguity Guard] UI changed after '{action}', but semantic verification FAILED. Rejecting mapping."
                )
                failed_positions.add((best_node["x"], best_node["y"]))
                engine.reject_click(intent_description)

                # Safety: If we're not where we expect to be, try to back out to clear any accidentally opened menus
                logger.info("🛡️ [Safety Reset] Pressing BACK to clear potential accidental menu/sub-view.")
                self.device.press("back")
                time.sleep(1.0)

                if attempt < max_retries:
                    logger.info(
                        f"🔄 [Autonomy] Negative learning acquired. Retrying transition '{action}' ({attempt + 1}/{max_retries})..."
                    )
                    continue
                else:
                    return False

        return False

    def _repair_transition(self, action: str):
        """
        If a transition fails, the CompilerEngine is invoked to figure out the new UI layout
        and write a new rule for `action`.
        """
        from GramAddict.core.dojo_engine import DojoEngine

        dojo = DojoEngine.get_instance(self.device)

        logger.warning(f"⛩️ [Dojo] Enqueuing auto-labeling job for missing '{action}'.", extra={"color": "\x1b[36m"})
        context_xml = self.device.dump_hierarchy()

        dojo.submit_snapshot(
            heuristic_name=action,
            context_xml=context_xml,
            intent_prompt=f"Find the button that performs: {action}. Be extremely robust against structural UI changes.",
        )
