"""
Physics — Humanized Input Simulation.

All low-level device interaction functions that simulate human touch behavior:
scroll, click, swipe, horizontal swipe.

Uses Biomechanical Bézier curve generation for organic, non-linear touch paths
with sigmoid velocity profiles and Gaussian pressure variation.
Falls back to linear `input swipe` when sendevent is unavailable.

Extracted from bot_flow.py to enable isolated testing and reuse.
"""

import logging
import random
from time import sleep

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

logger = logging.getLogger(__name__)


def humanized_scroll(device, is_skip=False, resonance_score=None):
    """
    Simulates a human thumb flick to trigger native scroll-snapping.

    Uses Bézier curves for non-linear path generation and sigmoid timing
    for organic acceleration/deceleration. The PhysicsBody provides
    session-persistent anchor drift and fatigue modeling.

    resonance_score: Optional. If high, increases chance of 'Correction' (Reverse scroll).
    """
    info = device.get_info()
    w, h = info.get("displayWidth", 1080), info.get("displayHeight", 2400)
    body = PhysicsBody.get_session_instance(device)
    injector = SendEventInjector.get_instance(device)

    # 1. Calculate Base Probability for Correction (Reverse Flick)
    # Default 15% for doomscroll corrections.
    # If resonance is high, we scale up to 45% chance to "Look back" at what we just passed.
    correction_prob = 0.15
    if resonance_score is not None and resonance_score > 0.7:
        correction_prob = 0.15 + (resonance_score - 0.7) * 1.0  # 0.7=0.15, 1.0=0.45

    # Start position from PhysicsBody (session-aware, drifting)
    start_x, start_y = body.get_scroll_start()
    end_x = start_x + random.gauss(0, w * 0.008)  # Slight horizontal drift

    if is_skip:
        # Aggressive fast fling to skip quickly. NO CORRECTIONS.
        distance = int(h * random.uniform(0.6, 0.75))
        duration = random.uniform(150, 250)  # slightly longer to ensure smooth fling registration
        end_y = start_y - distance
    else:
        # Playful, organic human scrolling
        play_choice = random.random()

        if play_choice > (1.0 - (correction_prob / 3.0)) or play_choice > 0.95:
            # "Go back" / Scroll UP
            start_y = int(h * random.uniform(0.20, 0.40))
            distance = int(h * random.uniform(0.30, 0.50))
            duration = random.uniform(100, 180)
            end_y = min(start_y + distance, h - 10)
            logger.info(f"🪀 [Playful Scroll] Correction (Prob: {correction_prob:.2f}) — Flicking back up...")

        elif play_choice > 0.85:
            # "Reading Jitter" / Playing around (10% chance)
            distance = int(h * random.uniform(0.05, 0.15))
            duration = random.uniform(300, 600)
            if random.random() > 0.5:
                end_y = start_y - distance
            else:
                start_y = int(h * random.uniform(0.30, 0.50))
                end_y = start_y + distance
            logger.info("🪀 [Playful Scroll] Micro-jitter...")

        elif play_choice > 0.25:
            # "Lazy Flick" - Post to Post Snap (60% chance)
            distance = int(h * random.uniform(0.15, 0.25))
            duration = random.uniform(150, 350)
            end_y = start_y - distance

        else:
            # Medium classic swipe (25% chance)
            distance = int(h * random.uniform(0.30, 0.45))
            duration = random.uniform(250, 500)
            end_y = start_y - distance

    # --- Behavioral Micro-Patterns (new human behaviors) ---
    behavior = None if is_skip else _select_scroll_behavior()

    if behavior == "pre_touch_dwell":
        # Finger lands on glass before swiping (50-200ms dwell)
        logger.debug("🦴 [Biomechanics] Pre-touch dwell...")

    if behavior == "overshoot_correction":
        # Scroll too far, then micro-correct back
        logger.debug("🦴 [Biomechanics] Overshoot + Correction pattern")
        # Extend original distance, then we'll add a correction swipe after
        original_end_y = end_y
        overshoot = int(h * random.uniform(0.08, 0.15))
        if end_y < start_y:
            end_y -= overshoot  # Scroll further down
        else:
            end_y += overshoot  # Scroll further up

    if behavior == "reading_pause":
        logger.debug("🦴 [Biomechanics] Mid-scroll reading pause")

    # --- Generate Bézier Curve ---
    points = BezierGesture.scroll_curve((start_x, start_y), (int(end_x), end_y), body)
    timing = BezierGesture.compute_sigmoid_timing(len(points), duration)

    # Pre-touch dwell: hold finger on glass before moving
    if behavior == "pre_touch_dwell":
        pre_dwell_ms = random.uniform(0.05, 0.2)
        # Insert a stationary point at the beginning
        points.insert(0, points[0])
        timing.insert(0, pre_dwell_ms)

    # Reading pause: insert a long dwell mid-gesture
    if behavior == "reading_pause":
        mid = len(points) // 2
        pause_point = points[mid]
        pause_duration = random.uniform(0.5, 2.0)
        points.insert(mid + 1, pause_point)
        timing.insert(mid, pause_duration)

    # --- Inject Gesture ---
    injector.inject_gesture(points, timing, touch_major=body.get_touch_major())

    # Post-gesture: overshoot correction
    if behavior == "overshoot_correction":
        sleep(random.uniform(0.3, 0.6))
        # Small corrective scroll back
        corr_start_x, corr_start_y = body.get_scroll_start()
        corr_distance = int(h * random.uniform(0.05, 0.1))
        if original_end_y < start_y:
            corr_end_y = corr_start_y + corr_distance  # Scroll back up
        else:
            corr_end_y = corr_start_y - corr_distance  # Scroll back down

        corr_points = BezierGesture.scroll_curve(
            (corr_start_x, corr_start_y), (corr_start_x, corr_end_y), body, n_points=6
        )
        corr_timing = BezierGesture.compute_sigmoid_timing(len(corr_points), 200)

        injector.inject_gesture(corr_points, corr_timing, touch_major=body.get_touch_major())


def humanized_click(device, x, y, double=False, sleep_mod=1.0):
    """Simulates a human tap with biomechanical jitter and micro-drift."""

    def single_tap():
        # Apply biomechanical jitter
        jx = int(x + random.gauss(0, 5))
        jy = int(y + random.gauss(0, 5))
        device.shell(f"input tap {jx} {jy}")

    if double:
        # For double tap, the timing is extremely critical (<300ms between taps).
        # We bypass sendevent overhead and batch two input taps directly in the shell.
        device.shell(f"input tap {int(x)} {int(y)} && input tap {int(x)} {int(y)}")

    else:
        single_tap()


def humanized_horizontal_swipe(device, start_x, end_x, y, duration_ms):
    """Simulates a human horizontal swipe with thumb arc simulation."""
    body = PhysicsBody.get_session_instance(device)
    injector = SendEventInjector.get_instance(device)

    # Apply jitter to start/end positions
    noise_y = random.randint(-15, 15)
    actual_start_x = int(start_x) + random.randint(-10, 10)
    actual_end_x = int(end_x) + random.randint(-20, 20)
    actual_y = int(y) + noise_y

    # Timing wobble (+/- 30%)
    actual_duration = int(duration_ms * random.uniform(0.7, 1.3))

    points = BezierGesture.horizontal_swipe_curve((actual_start_x, actual_y), (actual_end_x, actual_y), body)
    timing = BezierGesture.compute_sigmoid_timing(len(points), actual_duration)

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


def _select_scroll_behavior():
    """
    Selects a micro-behavior pattern for the current scroll gesture.

    Returns one of:
    - None: standard scroll (most common)
    - "pre_touch_dwell": finger lands on glass before swiping
    - "overshoot_correction": scrolls too far, then corrects back
    - "reading_pause": finger pauses mid-scroll
    """
    roll = random.random()
    if roll < 0.08:
        return "pre_touch_dwell"
    elif roll < 0.20:
        return "overshoot_correction"
    elif roll < 0.35:
        return "reading_pause"
    return None
