import hashlib
import logging
import re
import xml.etree.ElementTree as ET
from enum import Enum
from typing import Any, Dict

logger = logging.getLogger(__name__)


class ScreenType(Enum):
    HOME_FEED = "home_feed"
    EXPLORE_GRID = "explore_grid"
    REELS_FEED = "reels_feed"
    OWN_PROFILE = "own_profile"
    OTHER_PROFILE = "other_profile"
    POST_DETAIL = "post_detail"
    STORY_VIEW = "story_view"
    DM_INBOX = "dm_inbox"
    DM_THREAD = "dm_thread"
    SEARCH_RESULTS = "search_results"
    FOLLOW_LIST = "follow_list"
    COMMENTS = "comments"
    MODAL = "modal"
    DANGER_ACTION_BLOCKED = "danger_action_blocked"
    FOREIGN_APP = "foreign_app"
    UNKNOWN = "unknown"


class ScreenIdentity:
    """
    Understands what screen the bot is on by analyzing the XML dump.
    NO hardcoded states — purely structural analysis.

    This is the bot's EYES. It answers: "What do I see right now?"
    """

    def __init__(self, bot_username: str):
        self.bot_username = bot_username.lower()
        try:
            from GramAddict.core.qdrant_memory import ScreenMemoryDB

            self.screen_memory = ScreenMemoryDB()
        except ImportError:
            self.screen_memory = None

    def identify(self, xml_dump: str, screenshot_b64: str = None) -> Dict[str, Any]:
        """
        Analyzes an XML dump and returns a complete screen description.

        Returns:
            {
                'screen_type': ScreenType,
                'available_actions': ['tap like button', 'tap explore tab', ...],
                'selected_tab': 'feed_tab' | 'search_tab' | ...,
                'context': {'username': '...', 'post_count': '...', ...}
            }
        """
        if not xml_dump or not isinstance(xml_dump, str):
            return self._empty_screen()

        try:
            clean = re.sub(r"<\?xml.*?\?>", "", xml_dump).strip()
            root = ET.fromstring(clean)
        except Exception:
            return self._empty_screen()

        # Extract structural signals
        packages = set()
        resource_ids = set()
        content_descs = []
        texts = []
        selected_tab = None
        clickable_elements = []

        app_id = "com.instagram.android"

        for elem in root.iter("node"):
            pkg = elem.get("package", "")
            if pkg:
                packages.add(pkg)

            rid = elem.get("resource-id", "").strip()
            text = elem.get("text", "").strip()
            desc = elem.get("content-desc", "").strip()
            clickable = elem.get("clickable", "false") == "true"
            selected = elem.get("selected", "false") == "true"
            bounds = elem.get("bounds", "")

            if rid:
                # Normalize: "com.instagram.android:id/feed_tab" → "feed_tab"
                short_id = rid.split("/")[-1] if "/" in rid else rid
                resource_ids.add(short_id)

                # Track which tab is selected
                if selected and short_id in ("feed_tab", "search_tab", "clips_tab", "profile_tab", "direct_tab"):
                    selected_tab = short_id

            if text:
                texts.append(text)
            if desc:
                content_descs.append(desc)

            if clickable and bounds:
                match = re.match(r"\[(\d+),(\d+)\]\[(\d+),(\d+)\]", bounds)
                if match:
                    left, t, r, b = map(int, match.groups())
                    cx, cy = (left + r) // 2, (t + b) // 2
                    clickable_elements.append(
                        {
                            "text": text,
                            "desc": desc,
                            "id": rid.split("/")[-1] if "/" in rid else rid,
                            "x": cx,
                            "y": cy,
                            "bounds": bounds,
                        }
                    )

        from GramAddict.core.situational_awareness import SituationalAwarenessEngine

        sae = SituationalAwarenessEngine.get_instance()
        signature = sae._compress_xml(xml_dump) if sae else self._compute_signature(resource_ids, content_descs, texts)

        # ── Foreign app check ──
        if app_id not in packages:
            return {
                "screen_type": ScreenType.FOREIGN_APP,
                "available_actions": ["press back", "force start instagram"],
                "selected_tab": None,
                "context": {"packages": list(packages)},
                "signature": signature,
            }

        desc_lower = " ".join(content_descs).lower()
        text_lower = " ".join(texts).lower()
        ids_str = " ".join(resource_ids).lower()

        # ── Identify screen type from structural signals ──
        screen_type = self._classify_screen(
            resource_ids, content_descs, texts, selected_tab, desc_lower, text_lower, ids_str, signature, screenshot_b64
        )

        # ── Extract available actions from clickable elements ──
        available_actions = self._extract_available_actions(
            clickable_elements, resource_ids, content_descs, texts, screen_type
        )

        # ── Extract context ──
        context = self._extract_context(content_descs, texts, resource_ids, screen_type)

        return {
            "screen_type": screen_type,
            "available_actions": available_actions,
            "selected_tab": selected_tab,
            "context": context,
            "signature": signature,
        }

    def _classify_screen(
        self, ids, descs, texts, selected_tab, desc_lower, text_lower, ids_str, signature=None, screenshot_b64=None
    ):
        """
        Classify screen type using Semantic Memory with LLM fallback — NO hardcoded states."""

        # Priority 0: Fetch Qdrant Semantic Cache
        # We fetch this early to see if there is a 'NORMAL' override for the MODAL check.
        # We DO NOT let this override deterministic structural heuristics! Fuzzy vector matching
        # can easily confuse HOME_FEED and OWN_PROFILE if the bottom navigation bar is identical.
        cached_type_str = None
        if signature and self.screen_memory and self.screen_memory.is_connected:
            cached_type_str = self.screen_memory.get_screen_type(signature, similarity_threshold=0.92)

        is_normal_override = cached_type_str == "NORMAL"

        # Priority 1: Content-creation overlays that block ALL navigation.
        # These full-screen Instagram UIs have no navigation tabs and trap the bot.
        # Structural detection is O(1), zero LLM calls, and cannot be fooled.
        if not is_normal_override:
            modal_markers = (
                "quick_capture",
                "gallery_cancel_button",
                "creation_flow",
                "reel_camera",
                "survey_overlay_container",
                "interstitial_container",
                "nux_overlay",
                "rating_prompt",
                "feedback_dialog",
                "action_bar_browser_container",
            )
            if any(marker in ids_str for marker in modal_markers):
                logger.info("🛡️ [ScreenIdentity] Modal/Interstitial overlay detected → MODAL")
                return ScreenType.MODAL

        # Action Blocked Detection (O(1) fast-path)
        # Prevents LLM hallucinations for system-level traps that block the entire flow.
        danger_markers = (
            "try again later",
            "action blocked",
            "we restrict certain activity",
            "to protect our community",
        )
        if "bottom_sheet_container" in ids and any(d in text_lower or d in desc_lower for d in danger_markers):
            logger.info("🛡️ [ScreenIdentity] Critical obstacle detected → DANGER_ACTION_BLOCKED")
            return ScreenType.DANGER_ACTION_BLOCKED

        # Priority 2: Structural Heuristics (100% Deterministic)
        if "unified_follow_list_tab_layout" in ids or "follow_list_container" in ids:
            return ScreenType.FOLLOW_LIST

        # Profile structural markers
        PROFILE_MARKERS = (
            "profile_header_container",
            "row_profile_header_imageview",
            "profile_tabs_container",
            "profile_header_name",
        )
        if any(marker in ids for marker in PROFILE_MARKERS):
            own_profile_texts = ("edit profile", "share profile")
            if selected_tab == "profile_tab" or any(m in desc_lower or m in text_lower for m in own_profile_texts):
                return ScreenType.OWN_PROFILE
            return ScreenType.OTHER_PROFILE

        # Reels structural markers — present even when Instagram hides the tab bar
        # in full-screen Reels viewing. Without this, selected_tab=None → UNKNOWN.
        REELS_MARKERS = ("clips_viewer_container", "root_clips_layout", "clips_linear_layout_container")
        if any(marker in ids for marker in REELS_MARKERS):
            return ScreenType.REELS_FEED

        # DM thread detection — Semantic app-agnostic markers (chat input fields)
        chat_input_markers = ["Message...", "Nachricht...", "Type a message", "Nachricht senden", "Send a message"]
        if any(marker in texts for marker in chat_input_markers) or "direct_thread_header" in ids:
            return ScreenType.DM_THREAD

        # POST_DETAIL vs HOME_FEED: Both have row_feed_* markers. The differentiator
        # is that HOME_FEED has the main_feed_action_bar (top bar with 'Instagram' title).
        # POST_DETAIL lacks this because it shows a single expanded post.
        # Note: We MUST NOT use `not selected_tab` here — posts opened from feed
        # retain the feed_tab as selected, which previously caused misclassification.
        if "row_feed_button_like" in ids and "row_feed_photo_profile_name" in ids:
            if "main_feed_action_bar" not in ids:
                return ScreenType.POST_DETAIL

        # Story view structural markers — present in full-screen story viewer.
        # Stories hide the navigation tab bar, so selected_tab is always None.
        # Must be checked BEFORE tab-based fallbacks to prevent UNKNOWN classification.
        STORY_MARKERS = (
            "reel_viewer_media_layout",
            "reel_viewer_header",
            "reel_viewer_progress_bar",
            "reel_viewer_root",
            "story_viewer_container",
            "reel_viewer_content_layout",
        )
        if any(marker in ids for marker in STORY_MARKERS):
            return ScreenType.STORY_VIEW
        # Fallback: content-desc "Like Story" or "Send story" confirms story context
        if "like story" in desc_lower or "send story" in desc_lower or "nachricht senden" in desc_lower:
            return ScreenType.STORY_VIEW

        if selected_tab == "feed_tab":
            return ScreenType.HOME_FEED
        if selected_tab == "clips_tab":
            return ScreenType.REELS_FEED
        if selected_tab == "search_tab":
            return ScreenType.EXPLORE_GRID
        if "action_bar_search_edit_text" in ids:
            return ScreenType.EXPLORE_GRID
        if selected_tab == "profile_tab":
            return ScreenType.OWN_PROFILE
        if selected_tab == "direct_tab":
            return ScreenType.DM_INBOX
        if "message_input" in ids:
            return ScreenType.DM_INBOX  # Fallback for DM thread as inbox

        # End of structural heuristics

        # Priority 3: Cached Semantic Type (If deterministic heuristics failed)
        if cached_type_str and cached_type_str != "NORMAL":
            try:
                cached_type = ScreenType[cached_type_str]
                # Enforce absolute structural parity: Story and Reels must have their structural markers.
                # If they reached Priority 3, it means Priority 2 failed to find their markers.
                # Therefore, any cache telling us this is a Story/Reel without those markers is hallucinating.
                if cached_type in (ScreenType.STORY_VIEW, ScreenType.REELS_FEED):
                    logger.warning(
                        f"⚠️ [ScreenIdentity] Rejecting cached {cached_type.name} due to missing structural markers."
                    )
                else:
                    return cached_type
            except KeyError:
                pass

        # Priority 4: Semantic VLM Classification Fallback
        if not screenshot_b64 and getattr(self, "device", None) is not None:
            screenshot_b64 = self.device.get_screenshot_b64()

        from GramAddict.core.config import Config
        from GramAddict.core.llm_provider import query_telepathic_llm

        cfg = Config()
        if not hasattr(cfg, "args"):
            raise RuntimeError("Config().args not initialized — cannot resolve AI model. Fail Fast.")
        url = getattr(cfg.args, "ai_telepathic_url")
        model = getattr(cfg.args, "ai_telepathic_model")

        layout_context = (
            f"Selected Tab: {selected_tab}\nResource IDs: {list(ids)}\nVisible Texts context: {texts[:10]}\n"
        )
        prompt = (
            f"Identify the Instagram screen layout type based on the provided screenshot and structural signals.\n"
            f"Valid types: {[t.name for t in ScreenType]}\n"
            f"Context:\n{layout_context}\n"
            f"Reply ONLY with the exact matching enum Type Name string, or 'UNKNOWN' if no type matches."
        )

        try:
            response = query_telepathic_llm(
                model=model,
                url=url,
                system_prompt=prompt,
                user_prompt="Classify this screen layout.",
                images_b64=[screenshot_b64] if screenshot_b64 else None,
                temperature=0.0,
                use_local_edge=True,
            )

            result = response.strip().upper() if response else "UNKNOWN"

            for t in ScreenType:
                if t.name in result:
                    if is_normal_override and t == ScreenType.MODAL:
                        # Prevent the LLM from hallucinating an obstacle if explicitly verified as NORMAL
                        return ScreenType.UNKNOWN

                    # Enforce absolute structural parity: Story and Reels must have their structural markers.
                    if t in (ScreenType.STORY_VIEW, ScreenType.REELS_FEED):
                        logger.warning(
                            f"⚠️ [ScreenIdentity] Rejecting VLM hallucinated {t.name} due to missing structural markers."
                        )
                        return ScreenType.UNKNOWN

                    if signature and self.screen_memory:
                        self.screen_memory.store_screen(signature, t.name)
                    return t
        except Exception as e:
            import logging

            logging.getLogger(__name__).debug(f"LLM Classification failed: {e}")

        return ScreenType.UNKNOWN

    def _extract_available_actions(self, clickable_elements, resource_ids, content_descs, texts, screen_type):
        """Discover what actions are possible on this screen."""
        actions = []

        # Navigation tabs (always available when visible)
        tab_map = {
            "feed_tab": "tap home tab",
            "search_tab": "tap explore tab",
            "clips_tab": "tap reels tab",
            "profile_tab": "tap profile tab",
            "direct_tab": "tap messages tab",
        }
        for tab_id, action in tab_map.items():
            if tab_id in resource_ids:
                actions.append(action)

        # Screen-specific actions
        desc_lower = " ".join(content_descs).lower()
        text_lower = " ".join(texts).lower()

        if "like" in desc_lower:
            actions.append("tap like button")
        if "comment" in desc_lower:
            actions.append("tap comment button")
        if "share" in desc_lower:
            actions.append("tap share button")
        if "save" in desc_lower or "bookmark" in desc_lower:
            actions.append("tap save button")
        if "back" in desc_lower:
            actions.append("tap back button")
        has_following = any(
            "following" in e.get("text", "").lower() or "following" in e.get("desc", "").lower()
            for e in clickable_elements
        )
        if has_following:
            actions.append("tap following button")
        elif any(
            "follow" in e.get("text", "").lower()
            or "follow" in e.get("desc", "").lower()
            or "follow" in e.get("id", "").lower()
            for e in clickable_elements
        ):
            actions.append("tap follow button")

        if screen_type == ScreenType.OWN_PROFILE or screen_type == ScreenType.OTHER_PROFILE:
            if "message" in desc_lower:
                actions.append("tap message button")
            if (
                "following" in desc_lower
                or "following" in text_lower
                or "profile_header_following" in " ".join(resource_ids).lower()
            ):
                actions.append("tap following list")

        # Grid items
        if screen_type == ScreenType.EXPLORE_GRID:
            actions.append("tap first post")

        # Scroll
        actions.append("scroll down")
        actions.append("scroll up")
        actions.append("press back")

        return list(set(actions))  # Deduplicate

    def _extract_context(self, content_descs, texts, resource_ids, screen_type):
        """Extract meaningful context from the screen."""
        context = {}

        desc_text = " ".join(content_descs)

        # Username on profile
        username_match = re.search(r"(\w+)'s (?:profile|story|unseen story)", desc_text)
        if username_match:
            context["username"] = username_match.group(1)

        # Post/follower counts
        for d in content_descs:
            m = re.match(r"([\d,.]+K?M?)(\s*)(posts?|followers?|following)", d, re.IGNORECASE)
            if m:
                context[m.group(3).lower()] = m.group(1)

        # Like state
        for d in content_descs:
            if d.lower() == "liked":
                context["is_liked"] = True
            elif d.lower() == "like":
                context["is_liked"] = False

        return context

    def _compute_signature(self, resource_ids, content_descs, texts):
        """Compute a stable hash for this screen state (for Qdrant lookup)."""
        # Use sorted IDs + key content for stability
        sig_parts = sorted(resource_ids)[:20]
        sig_parts.extend(sorted(set(d.lower()[:30] for d in content_descs if len(d) > 2))[:10])
        sig = "|".join(sig_parts)
        return hashlib.sha256(sig.encode()).hexdigest()[:24]

    def _empty_screen(self):
        return {
            "screen_type": ScreenType.FOREIGN_APP,
            "available_actions": ["press back", "force start instagram"],
            "selected_tab": None,
            "context": {},
            "signature": "empty",
        }
