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

from GramAddict.core.perception.screen_identity import ScreenType

logger = logging.getLogger(__name__)


# ══════════════════════════════════════════════════════
# Categorical Ban Matrix — Structural Impossibility
# ══════════════════════════════════════════════════════
# These define WHERE each interaction intent is structurally possible.
# If a screen is NOT listed for an intent, the action is categorically banned.
# This is a WHITELIST: unlisted = impossible. No VLM, no learning, no Qdrant.
# This matrix is the Single Source of Truth for structural action plausibility.
ALLOWED_SCREENS: Dict[str, FrozenSet[ScreenType]] = {
    "like": frozenset(
        {
            ScreenType.HOME_FEED,
            ScreenType.POST_DETAIL,
            ScreenType.REELS_FEED,
            ScreenType.EXPLORE_GRID,  # After opening a post
            ScreenType.STORY_VIEW,  # toolbar_like_button exists on stories
        }
    ),
    "comment": frozenset(
        {
            ScreenType.HOME_FEED,
            ScreenType.POST_DETAIL,
            ScreenType.REELS_FEED,
            ScreenType.COMMENTS,
            ScreenType.STORY_VIEW,  # reel_viewer_comments_button + message_composer
        }
    ),
    "follow": frozenset(
        {
            ScreenType.OTHER_PROFILE,
            ScreenType.FOLLOW_LIST,
            ScreenType.STORY_VIEW,  # reel_header_unconnected_follow_button_stub
        }
    ),
    "unfollow": frozenset(
        {
            ScreenType.OTHER_PROFILE,
            ScreenType.FOLLOW_LIST,
        }
    ),
    "save": frozenset(
        {
            ScreenType.HOME_FEED,
            ScreenType.POST_DETAIL,
            ScreenType.REELS_FEED,
        }
    ),
    "repost": frozenset(
        {
            ScreenType.HOME_FEED,
            ScreenType.POST_DETAIL,
            ScreenType.REELS_FEED,
        }
    ),
    "share": frozenset(
        {
            ScreenType.HOME_FEED,
            ScreenType.POST_DETAIL,
            ScreenType.REELS_FEED,
            ScreenType.STORY_VIEW,  # toolbar_reshare_button exists on stories
        }
    ),
    "tap post username": frozenset(
        {
            ScreenType.HOME_FEED,
            ScreenType.POST_DETAIL,
            ScreenType.REELS_FEED,
            ScreenType.EXPLORE_GRID,
        }
    ),
}

# Intent keywords that trigger the categorical ban check
INTERACTION_KEYWORDS = frozenset(ALLOWED_SCREENS.keys())


class ContextGate:
    """
    Validates if an action (intent) is structurally possible on the current screen.
    This acts as a high-speed circuit breaker before invoking expensive VLM logic.

    Architecture: 2-Layer Cascade
    ─────────────────────────────
    Layer 0: Categorical Ban Matrix (O(1) dict lookup, zero dependencies)
             Blocks structurally impossible actions BEFORE any network call.
             e.g., "like" is impossible on STORY_VIEW — no like button exists.

    Layer 1: Qdrant Learned Failures (optional, requires running Qdrant)
             Blocks actions that have been learned to fail consistently.
             e.g., "tap follow" on OTHER_PROFILE if that profile's follow button
             is hidden behind a "Requested" state.
    """

    def __init__(self, context_memory=None):
        if context_memory is None:
            try:
                from GramAddict.core.qdrant_memory import ContextMemoryDB

                self.context_memory = ContextMemoryDB()
            except Exception:
                self.context_memory = None
        else:
            self.context_memory = context_memory

    def is_allowed(self, intent: str, screen_state: Dict[str, Any]) -> bool:
        """
        Evaluates the context gate.

        Args:
            intent: The action name (e.g. 'follow', 'comment', 'tap like button')
            screen_state: The result of ScreenIdentity.identify()

        Returns:
            bool: True if the action is plausible (or unknown), False if banned.
        """
        intent_lower = intent.lower()
        screen_type = screen_state.get("screen_type", ScreenType.UNKNOWN)

        # ── Layer 0: Categorical Ban Matrix (instant, no dependencies) ──
        matched_keyword = self._extract_interaction_keyword(intent_lower)
        if matched_keyword is not None and screen_type != ScreenType.UNKNOWN:
            allowed_screens = ALLOWED_SCREENS[matched_keyword]
            if screen_type not in allowed_screens:
                logger.debug(
                    f"🛡️ [ContextGate] BLOCKED '{intent}' on {screen_type.name} — "
                    f"structurally impossible (allowed: {[s.name for s in allowed_screens]})"
                )
                return False

        # ── Layer 1: Qdrant Learned Failures ──
        if (
            matched_keyword is not None
            and screen_type != ScreenType.UNKNOWN
            and self.context_memory is not None
            and getattr(self.context_memory, "is_connected", False)
        ):
            if not self.context_memory.is_allowed(intent_lower, screen_type.name):
                logger.debug(
                    f"🛡️ [ContextGate] BLOCKED '{intent}' on {screen_type.name} — " f"learned failure from Qdrant"
                )
                return False

        # ── Default: Allow (Exploration) ──
        return True

    def get_valid_screens(self, intent: str) -> Optional[FrozenSet[ScreenType]]:
        """
        Returns the set of screens where an interaction intent is structurally valid.
        Used by the Planner for auto-routing when the goal can't be achieved on
        the current screen.

        Returns:
            FrozenSet[ScreenType] if the intent maps to a known interaction, else None.
        """
        keyword = self._extract_interaction_keyword(intent.lower())
        if keyword is not None:
            return ALLOWED_SCREENS[keyword]
        return None

    @staticmethod
    def _extract_interaction_keyword(intent_lower: str) -> Optional[str]:
        """
        Extracts the primary interaction keyword from an intent string.
        Returns None if no interaction keyword is found (i.e., this is a navigation intent).

        Uses word-boundary matching to prevent false positives:
        - "follow" matches "follow user" but NOT "followers" or "following list"
        - "like" matches "like post" but NOT "likelihood"
        """
        import re

        for kw in INTERACTION_KEYWORDS:
            if re.search(rf"\b{kw}\b", intent_lower):
                return kw
        return None
