import logging
from typing import Any, Dict, List, Optional

from GramAddict.core.navigation.knowledge import NavigationKnowledge
from GramAddict.core.perception.screen_identity import ScreenType

logger = logging.getLogger(__name__)


class GoalPlanner:
    """
    Given a goal and current screen state, plans the next action.

    Uses Dynamic Discovery to navigate without hardcoded maps.
    """

    def __init__(self, username: str):
        self.knowledge = NavigationKnowledge(username)

    def plan_next_step(
        self,
        goal: str,
        screen: Dict[str, Any],
        explored_nav_actions: set = None,
        action_failures: dict = None,
        visited_screens: set = None,
    ) -> Optional[str]:
        """Plans the NEXT single action to take toward the goal."""
        screen_type = screen["screen_type"]
        available = screen.get("available_actions", [])
        context = screen.get("context", {})
        goal_lower = goal.lower()

        # ── 1. Check if goal is ALREADY achieved ──
        if self._is_goal_achieved(goal_lower, screen_type, context):
            logger.info(f"🎯 [GOAP] Goal '{goal}' already achieved on {screen_type.value}!")
            return None

        # (Phase 5: legacy _plan_goal_action static heuristics purged,
        # all intents fall through to VLM-driven Discovery in _plan_navigation)

        # ── 3. Am I on the right screen? If not, navigate there ──
        selected_tab = screen.get("selected_tab")
        nav_action = self._plan_navigation(
            goal_lower, screen_type, available, selected_tab, explored_nav_actions, action_failures, visited_screens
        )
        if nav_action:
            return nav_action

        # Final fallback: back-track, UNLESS back-tracking is a known trap on this screen!
        if not self.knowledge.is_trap(screen_type, "press back"):
            return "press back"

        # We are trapped! Can't go forward, can't go back!
        logger.error(f"💀 [GOAP] Completely trapped on {screen_type.name}. Forcing Instagram restart.")
        return "force start instagram"

    def _is_goal_achieved(self, goal: str, screen_type: ScreenType, context: dict) -> bool:
        """Check if the goal is already satisfied. Delegates to ScreenTopology SSOT."""
        from GramAddict.core.screen_topology import ScreenTopology

        # Interaction goals (context-specific, not navigation)
        if "like" in goal and context.get("is_liked") is True:
            return True
        if "view profile" in goal and screen_type in (ScreenType.OWN_PROFILE, ScreenType.OTHER_PROFILE):
            return True

        # Navigation goals — delegate to SSOT
        target = ScreenTopology.goal_to_target_screen(goal)
        if target and screen_type == target:
            return True

        return False

    def _plan_navigation(
        self,
        goal: str,
        screen_type: ScreenType,
        available: List[str],
        selected_tab: Optional[str] = None,
        explored_nav_actions: set = None,
        action_failures: dict = None,
        visited_screens: set = None,
    ) -> Optional[str]:
        """If we're on the wrong screen, figure out how to navigate.

        Strategy (priority order):
        1. HD Map (ScreenTopology BFS) — deterministic, pre-computed routes
        2. Learned Knowledge (Qdrant) — dynamic discovery from past sessions
        3. Autonomous Discovery — linguistic matching + VLM intent
        """
        from GramAddict.core.screen_topology import ScreenTopology

        # 0. Aversive Filter: Remove known traps from available actions
        safe_available = []
        for action in available:
            if not self.knowledge.is_trap(screen_type, action):
                safe_available.append(action)
            else:
                logger.debug(f"🛡️ [Aversive Filter] Masking trapped action: '{action}'")
        available = safe_available

        visited_screens = visited_screens or set()

        # 0b. No-Op Guard & Anti-Loop Guard:
        # - Strip tab actions that navigate to the CURRENT screen.
        # - Strip actions that navigate to PREVIOUSLY VISITED screens (except back-tracking).
        noop_actions = set()
        for action in available:
            expected = ScreenTopology.expected_screen_for_action(action, screen_type)
            if expected == screen_type:
                noop_actions.add(action)
                logger.debug(f"🛡️ [No-Op Guard] Stripping '{action}' — leads back to {screen_type.name}")
            elif expected in visited_screens and action != "press back":
                noop_actions.add(action)
                logger.debug(f"🛡️ [Anti-Loop Guard] Stripping '{action}' — leads to visited {expected.name}")

        # Also strip actions where the HD Map says they go TO the current screen from OTHER screens
        for src_screen, transitions in ScreenTopology.TRANSITIONS.items():
            if src_screen == screen_type:
                continue  # We already handled this screen's own transitions
            for action, dest in transitions.items():
                if dest == screen_type and action in available:
                    noop_actions.add(action)
                    logger.debug(
                        f"🛡️ [No-Op Guard] Stripping '{action}' — known to navigate to current {screen_type.name}"
                    )
                elif dest in visited_screens and action in available and action != "press back":
                    noop_actions.add(action)
                    logger.debug(f"🛡️ [Anti-Loop Guard] Stripping '{action}' — known to navigate to visited {dest.name}")

        available = [a for a in available if a not in noop_actions]

        # Build avoid_actions for HD Map route planning
        avoid_actions = (explored_nav_actions or set()).copy()
        if action_failures:
            for key, count in action_failures.items():
                if isinstance(key, tuple) and len(key) == 2:
                    scr, act = key
                    if scr == screen_type and count >= 2:  # MAX_RETRIES is 2 in goap
                        avoid_actions.add(act)
                else:
                    if count >= 2:
                        avoid_actions.add(key)

        target_screen = ScreenTopology.goal_to_target_screen(goal)

        # ── 1. HD Map Pre-Check for Dead Ends ──
        # If the topological map KNOWS the target is unreachable due to action_failures,
        # we must preempt the Brain from blindly routing into a dead end.
        if target_screen and target_screen != screen_type:
            route = ScreenTopology.find_route(screen_type, target_screen, avoid_actions=avoid_actions)
            if route is None and ScreenTopology.find_route(screen_type, target_screen):
                logger.warning(
                    f"🛡️ [HD Map] Target {target_screen.name} is unreachable due to masked edges! Preventing Brain from blind routing."
                )
                return None

        # ── 2. HD Map Routing (Primary Strategy for Navigation) ──
        # Ground UI transitions in structural invariants. If the topological map knows the route, use it.
        target_screen = ScreenTopology.goal_to_target_screen(goal)
        if target_screen and target_screen != screen_type:
            route = ScreenTopology.find_route(screen_type, target_screen, avoid_actions=avoid_actions)
            if route:
                next_action, next_screen = route[0]
                # Verify action isn't explored/trapped
                if next_action not in (explored_nav_actions or set()):
                    if not self.knowledge.is_trap(screen_type, next_action):
                        route_desc = " → ".join(s.name for _, s in route)
                        logger.info(
                            f"🗺️ [HD Map] Route: {screen_type.name} → {route_desc}. " f"Next action: '{next_action}'"
                        )
                        return next_action
                    else:
                        logger.warning(f"🛡️ [HD Map] Route action '{next_action}' is trapped. Skipping HD Map.")
                else:
                    logger.debug(
                        f"🛡️ [HD Map] Route action '{next_action}' already explored and failed. Skipping HD Map."
                    )

        # ── 3. Brain-Driven Decision Making (Fallback / Discovery) ──
        # For non-navigation goals or when the HD Map is incomplete.
        from GramAddict.core.navigation.brain import ask_brain_for_action

        brain_action = ask_brain_for_action(goal, screen_type.name, available, avoid_actions)
        if brain_action:
            logger.info(f"🧠 [Brain] Decided to execute: '{brain_action}' (to achieve: '{goal}')")
            return brain_action

        # ── 2. Learned Knowledge (Qdrant) ──
        required_screens = self.knowledge.get_requirements(goal)

        # ── 3. Autonomous Discovery (Blank Start fallback) ──
        if not required_screens:
            logger.info(f"🧠 [Nav Discovery] No known requirements for '{goal}'. Will attempt autonomous discovery.")

            # Return raw intent for TelepathicEngine discovery (VLM)
            if explored_nav_actions and goal in explored_nav_actions:
                logger.info(
                    f"🛑 [Nav Discovery] Autonomous intent '{goal}' already tried and failed/trapped. Yielding to back-tracking."
                )
                return None  # Don't return goal again — force fallback to press back
            else:
                return goal

        # 4. If we're already on an acceptable screen, no navigation needed
        if screen_type in required_screens:
            return None

        # 5. Find the action we need to take (from learned knowledge or HD map)
        for target_screen in required_screens:
            # Try HD Map first!
            route = ScreenTopology.find_route(screen_type, target_screen, avoid_actions=avoid_actions)
            if route:
                next_action, next_screen = route[0]
                if next_action not in (explored_nav_actions or set()):
                    if not self.knowledge.is_trap(screen_type, next_action):
                        logger.info(f"🧭 [Nav HD Map] Routing to required {target_screen.name} via '{next_action}'")
                        return next_action

            known_action = self.knowledge.get_action_for_screen(target_screen)

            if not known_action:
                logger.info(f"🧭 [Nav Discovery] Don't know action to reach {target_screen.name}. Asking VLM...")

                screen_friendly_name = target_screen.name.replace("_", " ").lower()
                goal_words = [w.rstrip("s") for w in screen_friendly_name.split() if len(w) > 3]

                for action in available:
                    if any(w in action.lower() for w in goal_words):
                        known_target = self.knowledge.get_screen_for_action(action)
                        if known_target and known_target != target_screen:
                            continue

                        logger.info(
                            f"🎯 [Nav Discovery] Linguistic match on available action! '{action}' aligns with '{screen_friendly_name}'"
                        )
                        return action

                return f"navigate to {screen_friendly_name}"
            else:
                if known_action in available:
                    logger.info(f"🧭 [Nav Knowledge] Navigating to {target_screen.name} via '{known_action}'")
                    return known_action

        # If no targeted navigation works, try going back first
        if "press back" in available:
            return "press back"

        return None
