import logging
import random
import re
import time
import uuid
from datetime import datetime

from GramAddict.core.physics.biomechanics import BezierGesture, PhysicsBody
from GramAddict.core.physics.sendevent_injector import SendEventInjector
from GramAddict.core.qdrant_memory import QdrantBase

logger = logging.getLogger(__name__)


class DarwinEngine(QdrantBase):
    """
    Project Singularity: Continuous Bayesian Evolutionary Engine V3 (Proof of Resonance).
    Determines mathematically how to act on a per-post basis, generating custom
    Dwell Times and nonlinear scroll sequences to maximize the RL Reward Matrix.
    """

    def __init__(self, username: str, config_path: str = "config.yml"):
        self.username = username
        self.config_path = config_path
        super().__init__(
            collection_name="bot_darwin_mdp_resonance", vector_size=5
        )  # 5 corresponds to behavior_bounds length

        # We replace naive percentages with Markovian Dwell Behaviors
        self.behavior_bounds = {
            "initial_dwell_sec": (1.0, 15.0, 2.0),
            "scroll_velocity": (0.1, 2.0, 0.3),  # 1.0 is normal
            "back_swipe_prob": (0.0, 0.4, 0.1),
            "profile_visit_prob": (0.0, 0.8, 0.2),
            "comment_read_dwell": (0.0, 20.0, 4.0),
        }
        self.current_behavior = {}

    def synthesize_interaction_profile(self, target_resonance: float, text_length: int = 0) -> dict:
        """
        Given an AI aesthetic resonance score (0.0 to 1.0) and caption length,
        this generates a deterministic topological interaction behavior.
        """
        history = self._get_historical_landscape()
        epsilon = 0.15  # 15% pure exploration

        if not history or random.random() < epsilon:
            logger.info("🧬 [Darwin Engine] EXPLORE: Generating chaotic non-linear behavioral vector.")
            center = {k: (v[0] + v[1]) / 2 for k, v in self.behavior_bounds.items()}
            self.current_behavior = self._mutate(center)
        else:
            # Exploitation: Nearest neighbor matching the resonance profile closely
            best_node = max(history, key=lambda x: x[1])  # x[1] is the Reward
            best_params = best_node[0]
            logger.info(
                f"🧬 [Darwin Engine] EXPLOIT: Adapting proven behavioral vector from highest Peak Reward ({best_node[1]:.2f})."
            )
            self.current_behavior = self._mutate(best_params)

        # Modulate behavior directly by resonance
        # E.g., if resonance is 0.9 (amazing post), read comments longer!
        self.current_behavior["initial_dwell_sec"] *= max(0.5, target_resonance * 1.5)
        self.current_behavior["profile_visit_prob"] *= max(0.2, target_resonance * 2.0)

        # ── Generative Dwell-Time ──
        # Humans take longer to finish "reading" long captions.
        # Average reading speed is ~15-20 chars per second.
        if text_length > 20:
            reading_latency = min(15.0, text_length / 25.0)  # Cap extra reading time at 15s
            logger.debug(
                f"🧬 [Darwin Engine] Generative Dwell spike: +{reading_latency:.1f}s (Caption: {text_length} chars)"
            )
            self.current_behavior["initial_dwell_sec"] += reading_latency

        # Clip bounds
        for k, (b_min, b_max, _) in self.behavior_bounds.items():
            self.current_behavior[k] = max(b_min, min(b_max, self.current_behavior[k]))

        return self.current_behavior

    def execute_proof_of_resonance(
        self,
        device,
        resonance: float,
        text_length: int = 0,
        nav_graph=None,
        configs=None,
        resonance_oracle=None,
        username=None,
        context_xml: str = "",
    ):
        """
        Translates the mathematical interaction profile directly into device actions
        to prove engagement to the platform's anti-bot heuristic algorithm.
        """
        profile = self.synthesize_interaction_profile(resonance, text_length=text_length)

        logger.info("🧬 [Darwin MDP] Executing Proof of Resonance Sequence...")

        # Pre-compute screen dimensions for all sub-phases
        info = device.get_info()
        h = info.get("displayHeight", 2400)
        w = info.get("displayWidth", 1080)

        # 1. Initial Dwell
        dwell = profile["initial_dwell_sec"]
        logger.debug(f"   -> Dwelling for {dwell:.1f}s")
        time.sleep(dwell)

        # 2. Non-linear cognitive latency (Micro-Jitters)
        if profile["scroll_velocity"] != 1.0:
            logger.debug(
                f"   -> Simulating cognitive read latency (Micro-Jitters, Velocity: {profile['scroll_velocity']:.2f})"
            )
            body = PhysicsBody.get_session_instance(device)
            injector = SendEventInjector.get_instance(device)

            # Thumb starts on the right side of the screen to avoid clicking polls/tags in the center
            cx = int(w * 0.8) + device.cm_to_pixels(random.uniform(-0.3, 0.3))
            cy = h // 2

            # Keep distance microscopic (0.1 to 0.3 cm) so we DO NOT lose visual alignment
            distance = device.cm_to_pixels(random.uniform(0.1, 0.3))
            duration = max(0.5, 1.0 / max(0.1, profile["scroll_velocity"]))
            start_y = int(cy + distance / 2)
            end_y = int(cy - distance / 2)

            # Use Bézier curve for the jitter
            points = BezierGesture.scroll_curve((cx, start_y), (cx, end_y), body, n_points=6)
            timing = BezierGesture.compute_sigmoid_timing(len(points), duration * 1000)

            injector.inject_gesture(points, timing, touch_major=body.get_touch_major())

        # 3. Micro Back-swipe (The Human Wobble)
        if random.random() < profile["back_swipe_prob"]:
            logger.debug("   -> Executing cognitive wobble (Trace swipe)")
            # small rapid corrective swipe (approx 0.4-0.8 cm downward slip to exceed Touch Slop)
            slip_distance = device.cm_to_pixels(random.uniform(0.4, 0.8))
            noise_x = device.cm_to_pixels(random.uniform(-0.2, 0.2))
            cx = w // 2 + device.cm_to_pixels(random.uniform(-0.5, 0.5))
            cy = h // 2

            dur_ms = int(random.uniform(200, 500))

            # Use physics-based injector instead of algorithmic 'input swipe'
            body = PhysicsBody.get_session_instance(device)
            injector = SendEventInjector.get_instance(device)
            start_pt = (int(cx), int(cy))
            end_pt = (int(cx + noise_x), int(cy + slip_distance))

            points = BezierGesture.scroll_curve(start_pt, end_pt, body, n_points=5)
            timing = BezierGesture.compute_sigmoid_timing(len(points), dur_ms)
            injector.inject_gesture(points, timing, touch_major=body.get_touch_major())

            time.sleep(random.uniform(0.5, 1.2))

        # 4. Comment depth simulation (probabilistic & resonance-correlated)
        if profile["comment_read_dwell"] > 1.0 and resonance > 0.4 and random.random() < 0.3:
            if nav_graph:
                if not self._has_comments(context_xml):
                    logger.debug("   -> 🚫 [Darwin Engine] Skipping comment depth simulation (Post has 0 comments).")
                else:
                    logger.debug(
                        f"   -> Opening comments section for {profile['comment_read_dwell']:.1f}s depth simulation"
                    )

                # Capture image context of post BEFORE opening comment sheet
                b64_img_payload = None
                if configs and getattr(configs.args, "ai_vision_context", False):
                    try:
                        import base64

                        raw = device.screenshot()
                        if raw:
                            import io

                            buf = io.BytesIO()
                            raw.save(buf, format="JPEG")
                            b64_img_payload = [base64.b64encode(buf.getvalue()).decode("utf-8")]
                            logger.debug(
                                "👁️ [Vision Context] Captured post screenshot for True Vision semantic analysis."
                            )
                    except Exception as e:
                        logger.warning(f"⚠️ [Vision Context] Failed to capture screenshot: {e}")

                success = nav_graph.do("tap comment button")
                if success:
                    # ---- Phase 10: RAG Comment Extraction ----
                    if configs and resonance_oracle and getattr(configs.args, "ai_learn_comments", False):
                        # Limit scraping to 15% to avoid mechanical persistence
                        if random.random() < 0.05:
                            logger.debug("   -> Dumping UI hierarchy for Comment Extraction...")
                            try:
                                xml_data = device.dump_hierarchy()
                                t0 = time.time()
                                resonance_oracle.extract_and_learn_comments(
                                    xml_data, configs, author=username or "unknown", images_b64=b64_img_payload
                                )
                                t1 = time.time()
                                remaining_sleep = profile["comment_read_dwell"] - (t1 - t0)
                                if remaining_sleep > 0:
                                    time.sleep(remaining_sleep)
                            except Exception as e:
                                logger.error(f"   -> Comment extraction failed: {e}")
                                time.sleep(profile["comment_read_dwell"])
                        else:
                            logger.debug("   -> Skipping RAG Extraction (Probabilistic Evasion)")
                            time.sleep(profile["comment_read_dwell"])
                    else:
                        time.sleep(profile["comment_read_dwell"])
                    # ------------------------------------------

                    logger.debug("   -> Closing comments section")
                    device.press("back")
                    time.sleep(1.0)
                    # Instead of relying on a fragile bottom_sheet_container ID,
                    # we verify if the feed is visible. If not, the comment sheet is still open (or keyboard).
                    ui_dump = device.dump_hierarchy()
                    from GramAddict.core.telepathic_engine import TelepathicEngine

                    telepath = TelepathicEngine.get_instance()
                    if not telepath.find_best_node(
                        ui_dump, "post like button heart", min_confidence=0.4, device=device
                    ):
                        logger.debug("   -> Not back on Home feed, pressing back again to close comment sheet/keyboard")
                        device.press("back")
                        time.sleep(1.0)
                else:
                    logger.debug(
                        f"   -> Could not find comment button, falling back to dwell simulation for {profile['comment_read_dwell']:.1f}s"
                    )
                    time.sleep(profile["comment_read_dwell"])
            else:
                logger.debug(f"   -> Simulating comment section processing for {profile['comment_read_dwell']:.1f}s")
                time.sleep(profile["comment_read_dwell"])

        logger.info("🧬 [Darwin MDP] Interaction sequence completed safely.")
        return profile

    def execute_micro_wobble(self, device):
        """
        Simulates a thumb resting or slightly shifting on the glass.
        Essential for breaking the 'robotically still' dwell periods.
        Uses PhysicsBody for handedness-aware direction and fatigue-scaled amplitude.
        """
        if random.random() < 0.2:  # 20% chance for a wobble during dwell
            logger.debug("🧬 [Ghost Protocol] Micro-Wobble triggered.")
            body = PhysicsBody.get_session_instance(device)
            injector = SendEventInjector.get_instance(device)

            info = device.get_info()
            info.get("displayWidth", 1080)
            h = info.get("displayHeight", 2400)

            # Start position from body (session-aware)
            cx, cy = body.get_scroll_start()
            # Override Y to center for wobble
            cy = h // 2

            # Fatigue scales wobble amplitude (tired = more sloppy)
            amplitude = 1.0 + body.fatigue * 0.5

            # Keep the shift small but above Android's touch slop threshold (~8dp)
            y_shift = device.cm_to_pixels(random.uniform(0.3, 0.6) * amplitude) * random.choice([1, -1])
            x_shift = device.cm_to_pixels(random.uniform(-0.2, 0.2) * amplitude)

            # Handedness bias: right-handers wobble right-down, left-handers left-down
            if body.handedness == "right":
                x_shift += device.cm_to_pixels(random.uniform(0, 0.1))
            else:
                x_shift -= device.cm_to_pixels(random.uniform(0, 0.1))

            end_x = int(cx + x_shift)
            end_y = int(cy + y_shift)

            points = BezierGesture.scroll_curve((cx, cy), (end_x, end_y), body, n_points=5)
            duration_ms = random.uniform(150, 300)
            timing = BezierGesture.compute_sigmoid_timing(len(points), duration_ms)

            injector.inject_gesture(points, timing, touch_major=body.get_touch_major())

    def _get_historical_landscape(self):
        try:
            records = self.client.scroll(collection_name=self.collection_name, limit=1000, with_payload=True)[0]
            return [(r.payload.get("params", {}), r.payload.get("reward", 0.0)) for r in records]
        except Exception:
            return []

    def _mutate(self, base_params: dict) -> dict:
        new_params = {}
        for key, (p_min, p_max, volatility) in self.behavior_bounds.items():
            base_val = base_params.get(key, (p_min + p_max) / 2)
            mutation = random.gauss(0, volatility)
            new_params[key] = round(base_val + mutation, 3)
        return new_params

    def select_arm_and_apply(self, args):
        """
        Multi-Armed Bandit (MAB) logic to select the most promising behavioral
        mutation strategy for the current account phase.
        """
        logger.info(f"🧬 [Darwin Engine] Applying MDP State channel for @{self.username}...")
        self.synthesize_interaction_profile(target_resonance=0.5)  # Initial neutral bias

    def evaluate_session_end(self, duration_minutes: float, followers_gained: int):
        if duration_minutes <= 0:
            duration_minutes = 1.0
        reward = (followers_gained / duration_minutes) * 10.0
        logger.info(
            f"🧬 [Darwin Engine] Session Evaluation: {followers_gained} followers gained in {duration_minutes:.1f}m. Reward: {reward:.2f}"
        )
        self.emit_reward_signal(followers_gained=followers_gained, block_warnings_seen=0)

    def emit_reward_signal(self, followers_gained: int, block_warnings_seen: int):
        if not self.current_behavior:
            return

        try:
            reward = followers_gained - (block_warnings_seen * 50)

            vector = []
            for k, (p_min, p_max, _) in self.behavior_bounds.items():
                val = self.current_behavior.get(k, p_min)
                norm = (val - p_min) / max(0.1, (p_max - p_min))
                vector.append(norm)

            p_id = str(uuid.uuid4())
            self.upsert_point(
                seed_string=p_id,
                vector=vector,
                payload={
                    "username": self.username,
                    "timestamp": datetime.now().isoformat(),
                    "params": self.current_behavior,
                    "reward": reward,
                },
                log_success=f"🧬 [Darwin Engine V3] MDP Reward Matrix stored. Reward Value: {reward:.2f}",
            )
        except Exception as e:
            logger.debug(f"🧬 [Darwin Engine] Failed to record reward: {e}")

    def _has_comments(self, xml_string: str) -> bool:
        """
        Heuristic to check if a post actually has comments to read.
        If it has 0 comments, checking them is suspicious bot behavior.

        Zero-Maintenance: Uses only English text and resource_id patterns.
        Resource IDs are locale-invariant. English text in content_desc
        is used by Instagram internally and is reliable.
        """
        low_xml = xml_string.lower()

        # 1. Explicit zero comments check (resource_id based + English fallback)
        if re.search(r"\b0\s*comment(?:s)?\b", low_xml):
            return False

        # 2. Check for "view all" or similar prominent comment link texts
        if "view all" in low_xml:
            return True
        if "view 1 comment" in low_xml:
            return True
        if "comment number is" in low_xml:
            return True

        # 3. Structural: comment_textview_layout is present with a count > 0
        has_number_of_comments = re.search(r"\b([1-9][0-9.,]*)\s*comment(?:s)?\b", low_xml)
        if has_number_of_comments:
            return True

        # 4. Structural: The comment button resource_id exists and has content
        if "row_feed_comment_textview_layout" in low_xml:
            return True

        # If no indicators are found, assume the post has 0 comments.
        return False

