import logging
import random

from colorama import Fore, Style

from GramAddict.core.session_state import SessionState

logger = logging.getLogger(__name__)

# Hard cap: maximum DM replies per inbox visit to prevent spam.
MAX_REPLIES_PER_INBOX_VISIT = 3

# Sentinel values that indicate missing message context.
_EMPTY_CONTEXT_SENTINELS = frozenset({"no previous context", "", "none", "n/a"})


# Structural resource-IDs that indicate a real "Send" button.
def _is_send_button(node: dict) -> bool:
    """Semantic verification: returns True if the node is identified as a Send button."""
    desc = (node.get("description") or node.get("desc", "")).lower()
    text = (node.get("text") or "").lower()
    rid = (node.get("id") or node.get("resource_id", "")).lower()

    # Accept if semantic markers indicate sending
    if any(m in rid for m in ["send", "composer_button"]):
        return True
    if any(m in desc for m in ["send", "absenden"]):
        return True
    if text == "send" or text == "absenden":
        return True
    return False


def _run_zero_latency_dm_loop(device, zero_engine, nav_graph, configs, session_state, current_target, cognitive_stack):
    """
    Executes the autonomous Direct Messaging logic in the Zero-Latency architecture.
    Assumes the bot is already at the "MessageInbox" UI state.

    Safety guarantees:
    - Refuses to execute if dm_reply plugin is disabled in config.
    - Skips threads with no extractable text context.
    - Structurally verifies the Send button before logging success.
    - Hard-caps replies per inbox visit to MAX_REPLIES_PER_INBOX_VISIT.
    """
    # ── Kill-Switch: Respect dm_reply.enabled config ──
    dm_plugin_config = configs.get_plugin_config("dm_reply")
    if not dm_plugin_config.get("enabled", False):
        logger.warning(
            "🛑 [DM Engine] dm_reply plugin is DISABLED in config. Refusing to process inbox.",
            extra={"color": f"{Fore.RED}"},
        )
        return "BOREDOM_CHANGE_FEED"

    logger.info(
        f"🧠 [DM Engine] Initiating inbox processing in {current_target}...",
        extra={"color": f"{Style.BRIGHT}{Fore.CYAN}"},
    )

    telepathic = cognitive_stack.get("telepathic")
    dopamine = cognitive_stack.get("dopamine")

    from GramAddict.core.bot_flow import _humanized_click, sleep
    from GramAddict.core.llm_provider import query_llm
    from GramAddict.core.stealth_typing import ghost_type

    # Initialize session limits if missing
    if not hasattr(session_state, "totalMessages"):
        session_state.totalMessages = 0

    failed_attempts = 0
    replies_this_visit = 0

    while not dopamine.is_app_session_over():
        # Limits check
        limit_val = session_state.check_limit(SessionState.Limit.PM)
        if isinstance(limit_val, tuple) and limit_val[0]:
            logger.info("🛑 Messaging limit reached for session.")
            return "BOREDOM_CHANGE_FEED"
        elif limit_val is True:
            return "BOREDOM_CHANGE_FEED"

        try:
            xml_dump = device.dump_hierarchy()

            # --- Zero Trust Structural Guard ---
            from GramAddict.core.perception.screen_identity import ScreenIdentity, ScreenType

            identity_engine = ScreenIdentity(getattr(configs.args, "username", ""))
            identity_engine.device = device
            screen_info = identity_engine.identify(xml_dump)

            screen_type = screen_info["screen_type"]
            is_inbox = screen_type == ScreenType.DM_INBOX
            is_thread = screen_type == ScreenType.DM_THREAD

            if is_thread:
                logger.warning("⚠️ [Structural Guard] DM Engine trapped in an open thread. Escaping...")
                device.press("back")
                from GramAddict.core.bot_flow import sleep

                sleep(1.5)
                continue

            if not is_inbox:
                # We have drifted somewhere entirely alien (like Privacy Settings)
                logger.error(
                    f"🛑 [Structural Guard] Alien context detected ({screen_type}). Not in Inbox. Triggering CONTEXT_LOST."
                )
                return "CONTEXT_LOST"
            # -----------------------------------

            # Step 1: Find unread conversation threads
            unread_threads = telepathic._extract_semantic_nodes(
                xml_dump, "find unread message threads or unread badges", threshold=0.7
            )

            if unread_threads and not unread_threads[0].get("skip"):
                target_node = unread_threads[0]
                logger.info("📨 Found unread message thread. Opening.")
                _humanized_click(device, target_node["x"], target_node["y"])
                sleep(2.0)

                # Step 2: Read the conversation context
                thread_xml = device.dump_hierarchy()
                msg_nodes = telepathic._extract_semantic_nodes(
                    thread_xml, "find the last received message text", threshold=0.6
                )

                context_text = "No previous context"
                if msg_nodes and not msg_nodes[0].get("skip") and msg_nodes[0].get("text"):
                    context_text = msg_nodes[0].get("text")

                logger.debug(f"Last received message context: {context_text}")

                # ── Context Guard: Skip threads with no extractable message ──
                if context_text.strip().lower() in _EMPTY_CONTEXT_SENTINELS:
                    logger.warning(
                        "⏭️ [DM Engine] Thread has no extractable message context (story reply / media-only). Skipping."
                    )
                    device.press("back")
                    sleep(1.5)
                    continue

                # Verify we aren't at limits before sending
                # ── Iteration Cap: Prevent DM spam ──
                if replies_this_visit >= MAX_REPLIES_PER_INBOX_VISIT:
                    logger.info(
                        f"🛑 [DM Engine] Reached max replies per inbox visit ({MAX_REPLIES_PER_INBOX_VISIT}). Exiting."
                    )
                    device.press("back")
                    sleep(1.0)
                    return "BOREDOM_CHANGE_FEED"

                # Configure models
                model = getattr(configs.args, "ai_condenser_model")
                url = getattr(configs.args, "ai_condenser_url")

                # Generate response
                prompt = f"You are replying to a direct message on Instagram. The last message you received was: '{context_text}'. Keep it short, casual, and friendly. Do not use hashtags."

                logger.info(">>> [DM Engine] ABOUT TO CALL LLM")
                response_dict = query_llm(
                    url=url,
                    model=model,
                    prompt=prompt,
                    format_json=False,
                    timeout=120,
                    max_tokens=100,
                    temperature=0.7,
                )
                logger.info(f">>> [DM Engine] LLM RETURNED: {response_dict}")

                if response_dict and "response" in response_dict:
                    response_text = response_dict["response"].strip()
                    # Find the input field
                    input_nodes = telepathic._extract_semantic_nodes(
                        thread_xml, "find the message input text field", threshold=0.7
                    )
                    if input_nodes and not input_nodes[0].get("skip"):
                        in_node = input_nodes[0]
                        _humanized_click(device, in_node["x"], in_node["y"])
                        sleep(1.0)

                        # Type the message
                        ghost_type(device, response_text, speed="fast")
                        sleep(1.0)

                        # Find Send button
                        send_xml = device.dump_hierarchy()
                        send_nodes = telepathic._extract_semantic_nodes(
                            send_xml, "find the send message button", threshold=0.8
                        )

                        if send_nodes and not send_nodes[0].get("skip"):
                            s_node = send_nodes[0]

                            # ── Send Button Structural Verification ──
                            if not _is_send_button(s_node):
                                s_rid = s_node.get("original_attribs", {}).get("resource-id", "unknown")
                                logger.warning(
                                    f"⚠️ [DM Engine] Refused to click non-Send element: {s_rid}. Aborting reply."
                                )
                            else:
                                _humanized_click(device, s_node["x"], s_node["y"])
                                logger.info(
                                    "✅ [DM Engine] Successfully sent a generated reply.",
                                    extra={"color": Fore.GREEN},
                                )
                                session_state.totalMessages += 1
                                replies_this_visit += 1
                                dm_memory = cognitive_stack.get("dm_memory")
                                if dm_memory:
                                    dm_memory.log_sent_dm("unknown_target", response_text, "", [])

                # Return back to inbox
                device.press("back")
                sleep(1.5)

                # If keyboard was open, the first back only closed it. Check if still in thread.
                check_xml = device.dump_hierarchy()
                from GramAddict.core.perception.screen_identity import ScreenIdentity, ScreenType

                check_identity = ScreenIdentity(getattr(configs.args, "username", ""))
                check_identity.device = device
                check_screen = check_identity.identify(check_xml)

                if check_screen["screen_type"] == ScreenType.DM_THREAD:
                    device.press("back")
                    sleep(1.0)

                dopamine.boredom += random.uniform(5.0, 15.0)
                failed_attempts = 0
            else:
                logger.info("📭 No unread threads found. Inbox clear.")
                dopamine.boredom += 50.0  # Inbox clear = massive boredom = change feed

            if dopamine.wants_to_change_feed() or dopamine.boredom >= 100:
                logger.info("🧠 [DM Engine] Interaction complete. Transitioning back from inbox.")
                device.press("back")  # Go back from inbox
                return "BOREDOM_CHANGE_FEED"

        except Exception as e:
            logger.error(f"⚠️ [Anomaly Handler] Exception in DM Loop: {e}")
            device.press("back")
            sleep(1.0)

            check_xml = device.dump_hierarchy()
            from GramAddict.core.perception.screen_identity import ScreenIdentity, ScreenType

            check_identity = ScreenIdentity(getattr(configs.args, "username", ""))
            check_identity.device = device
            check_screen = check_identity.identify(check_xml)

            if check_screen["screen_type"] == ScreenType.DM_THREAD:
                device.press("back")
                sleep(1.0)

            failed_attempts += 1
            if failed_attempts > 2:
                return "CONTEXT_LOST"

    if dopamine.is_app_session_over():
        return "SESSION_OVER"

    return "FEED_EXHAUSTED"
