import math
import time
import logging
import pydirectinput
from player_position import PlayerPosition

# 游戏朝向约定：正北=0°，正西=90°，正南=180°，正东=270°（逆时针递增）
# 游戏坐标约定：人物面朝正北，按w一秒，坐标y会增加0.1  反之减少0.1 。如果人物面朝正东，按w一秒，坐标x会增加0.1  如果人物面朝正西，按w一秒，坐标x会减少0.1 。
# 按键w前进，按键a左转，按键d右转，按键s后退 (转向时最好按0.5s就停止，以免转向过度)
# 当前人物的坐标和朝向根据player_position.py模块获取

class PlayerMovement:

    MOVE_SPEED = 0.1            # 每秒移动距离（坐标单位）
    TURN_RATE = 216.0           # 每秒转向角度（度）；实测约 216°/s（0.5s ≈ 108°）
    MAX_TURN_DURATION = 0.5     # 原地转向单次最长按键时间（秒），避免转向过度
    SMOOTH_TURN_DURATION = 0.15 # 边走边转时每次按键时长（秒）
    ANGLE_TOLERANCE = 5.0       # 朝向允许误差（度）
    COARSE_TURN_THRESHOLD = 90.0  # 超过此角度差则停下来原地粗对齐，再继续行走
    HEADING_CHECK_INTERVAL = 5    # 每隔几次位置读取才做一次朝向校正
    STUCK_CHECK_COUNT = 3         # 连续多少次坐标无明显位移则判定为卡死
    STUCK_MOVE_THRESHOLD = 0.05   # 每次检测周期内最小期望位移（坐标单位）
    # 实测方向：按 a 朝向减小（右转），按 d 朝向增大（左转）
    # 游戏朝向为顺时针：北=0°, 东=90°, 南=180°, 西=270°

    def __init__(self):
        self.player_position = PlayerPosition()
        self.logger = logging.getLogger(__name__)

    def get_required_heading(self, current_x: float, current_y: float,
                             target_x: float, target_y: float) -> float:
        """计算从当前位置朝向目标位置所需的游戏朝向角度。

        实测游戏规律：
          朝向 0°   → 南（y 减小），朝向 180° → 北（y 增加）
          朝向 90°  → 东（x 增加），朝向 270° → 西（x 减小）
        即：dx_actual ∝ sin(heading)，dy_actual ∝ -cos(heading)
        因此目标朝向 = atan2(dx, -dy)
        验证：需北移(dy>0) → atan2(0,-1)=180° ✓；需东移(dx>0) → atan2(1,0)=90° ✓
        """
        dx = target_x - current_x
        dy = target_y - current_y
        if dx == 0 and dy == 0:
            return 0.0
        return math.degrees(math.atan2(dx, -dy)) % 360

    def turn_to_heading(self, target_heading: float, max_attempts: int = 10) -> bool:
        """转向到目标朝向。

        按键 a 左转（朝向增大），按键 d 右转（朝向减小）。
        每次按键不超过 MAX_TURN_DURATION 秒，多次微调直到误差在 ANGLE_TOLERANCE 内。

        Args:
            target_heading: 目标朝向角度（0~360）
            max_attempts: 最大调整次数

        Returns:
            bool: 是否成功对准目标朝向
        """
        for attempt in range(max_attempts):
            current_heading = self.player_position.get_heading_with_retry()
            if current_heading is None:
                self.logger.warning("无法获取当前朝向，转向中止")
                return False

            diff = (target_heading - current_heading) % 360

            if diff <= self.ANGLE_TOLERANCE or diff >= (360 - self.ANGLE_TOLERANCE):
                self.logger.info(f"朝向已对齐：当前 {current_heading:.1f}°，目标 {target_heading:.1f}°")
                return True

            # 实测：d 使朝向增大，a 使朝向减小（与按键名称相反）
            # diff ≤ 180：需增大朝向 → 按 d；diff > 180：需减小朝向 → 按 a
            if diff <= 180:
                key, angle_to_turn = 'd', diff
            else:
                key, angle_to_turn = 'a', 360 - diff

            turn_duration = min(angle_to_turn / self.TURN_RATE, self.MAX_TURN_DURATION)
            self.logger.info(
                f"[转向 {attempt + 1}] 当前 {current_heading:.1f}° → 目标 {target_heading:.1f}°，"
                f"按 '{key}' {turn_duration:.2f}s（需转 {angle_to_turn:.1f}°）"
            )
            pydirectinput.keyDown(key)
            time.sleep(turn_duration)
            pydirectinput.keyUp(key)
            time.sleep(0.15)  # 等待游戏刷新朝向

        self.logger.warning(f"转向失败：超过最大尝试次数 {max_attempts}")
        return False

    def move_forward(self, duration: float):
        """按住 w 键向前移动指定时间。

        Args:
            duration: 移动时间（秒）
        """
        self.logger.info(f"向前移动 {duration:.2f}s")
        pydirectinput.keyDown('w')
        time.sleep(duration)
        pydirectinput.keyUp('w')

    def _escape_stuck(self):
        """卡死脱困组合拳：后退 + 随机转向 + 跳跃。

        当连续多次检测到坐标无明显位移时调用，尝试脱离障碍物。
        """
        import random
        self.logger.warning("检测到角色卡死，执行脱困动作")

        # 1. 松开前进键，后退 0.8s
        pydirectinput.keyUp('w')
        pydirectinput.keyDown('s')
        time.sleep(0.8)
        pydirectinput.keyUp('s')

        # 2. 随机左转或右转 0.3~0.5s
        turn_key = random.choice(['a', 'd'])
        turn_dur = random.uniform(0.3, 0.5)
        pydirectinput.keyDown(turn_key)
        time.sleep(turn_dur)
        pydirectinput.keyUp(turn_key)

        # 3. 跳跃（按空格）同时向前冲 0.5s
        pydirectinput.keyDown('w')
        pydirectinput.press('space')
        time.sleep(0.5)
        pydirectinput.keyUp('w')

        self.logger.info("脱困动作完成，重新开始前进")

    def move_to(self, target_x: float, target_y: float,
                position_tolerance: float = 0.5, max_iterations: int = 100) -> bool:
        """平滑移动人物到目标坐标（边走边转向）。

        策略：
        1. 起步前若角度偏差 > COARSE_TURN_THRESHOLD，先原地粗对齐；
        2. 按住 w 持续前进，每次读取朝向后同时按 a/d 微调方向（边走边转）；
        3. 若行走中出现大角度偏差（绕过障碍等），短暂停步原地修正后继续；
        4. 到达判定半径内松开 w，结束移动。

        Args:
            target_x, target_y: 目标坐标
            position_tolerance: 到达判定半径（坐标单位），默认 0.5
            max_iterations: 最大循环次数，防止死循环

        Returns:
            bool: 是否成功到达目标位置
        """
        self.logger.info(f"开始平滑移动到目标位置 ({target_x}, {target_y})")

        # 起步前先判断是否已在目标范围内，避免重复调用时原地乱转
        init_pos = self.player_position.get_position_with_retry()
        if init_pos is None:
            self.logger.warning("无法获取当前坐标，移动中止")
            return False
        if (abs(target_x - init_pos[0]) <= position_tolerance and
                abs(target_y - init_pos[1]) <= position_tolerance):
            self.logger.info(f"已在目标位置 ({target_x}, {target_y}) 范围内，无需移动")
            return True

        # 起步前粗对齐：角度偏差过大时先原地转向，避免一开始就走偏
        init_heading = self.player_position.get_heading_with_retry()
        if init_heading is not None:
            init_required = self.get_required_heading(init_pos[0], init_pos[1], target_x, target_y)
            init_diff = (init_required - init_heading) % 360
            init_abs_diff = init_diff if init_diff <= 180 else 360 - init_diff
            if init_abs_diff > self.COARSE_TURN_THRESHOLD:
                self.logger.info(f"起步角度差 {init_abs_diff:.1f}°，先原地粗对齐")
                if not self.turn_to_heading(init_required):
                    self.logger.warning("起步粗对齐失败，移动中止")
                    return False

        # 按住 w 开始持续前进
        pydirectinput.keyDown('w')
        # 卡死检测：记录最近 STUCK_CHECK_COUNT 次坐标，用于判断是否停滞
        recent_positions = []
        try:
            for iteration in range(max_iterations):
                # 每隔 HEADING_CHECK_INTERVAL 次才读一次朝向并校正
                # 读取当前位置（OCR 耗时期间 w 持续按住，角色一直在走）
                current_pos = self.player_position.get_position_with_retry()
                if current_pos is None:
                    self.logger.warning("无法获取当前坐标，移动中止")
                    return False

                current_x, current_y = current_pos
                dx = target_x - current_x
                dy = target_y - current_y
                distance = math.sqrt(dx ** 2 + dy ** 2)

                self.logger.info(
                    f"[平滑 {iteration + 1}] 当前 ({current_x}, {current_y})，"
                    f"目标 ({target_x}, {target_y})，剩余距离 {distance:.2f}"
                )

                # 卡死检测：维护滑动窗口，比较最新与最旧坐标的位移
                recent_positions.append((current_x, current_y))
                if len(recent_positions) > self.STUCK_CHECK_COUNT:
                    recent_positions.pop(0)
                if len(recent_positions) == self.STUCK_CHECK_COUNT:
                    oldest_x, oldest_y = recent_positions[0]
                    moved = math.sqrt((current_x - oldest_x) ** 2 + (current_y - oldest_y) ** 2)
                    if moved < self.STUCK_MOVE_THRESHOLD:
                        self._escape_stuck()
                        recent_positions.clear()
                        pydirectinput.keyDown('w')

                # x 和 y 都在容差范围内即视为到达
                if abs(dx) <= position_tolerance and abs(dy) <= position_tolerance:
                    self.logger.info(f"已到达目标位置 ({target_x}, {target_y})")
                    return True

                # 接近目标时松开 w 停下，再做一次静止判定，避免冲过头后死循环
                if distance <= position_tolerance * 2:
                    pydirectinput.keyUp('w')
                    time.sleep(0.2)
                    final_pos = self.player_position.get_position_with_retry()
                    if final_pos is not None:
                        fdx = target_x - final_pos[0]
                        fdy = target_y - final_pos[1]
                        if abs(fdx) <= position_tolerance and abs(fdy) <= position_tolerance:
                            self.logger.info(f"已到达目标位置 ({target_x}, {target_y})")
                            return True
                    # 静止后仍未到达，重新按住 w 继续前进
                    pydirectinput.keyDown('w')

                # 每隔 HEADING_CHECK_INTERVAL 次读一次朝向，减少 OCR 频率
                if iteration % self.HEADING_CHECK_INTERVAL != 0:
                    continue

                current_heading = self.player_position.get_heading_with_retry()
                if current_heading is None:
                    continue

                required_heading = self.get_required_heading(current_x, current_y, target_x, target_y)
                diff = (required_heading - current_heading) % 360
                abs_diff = diff if diff <= 180 else 360 - diff

                if abs_diff > self.COARSE_TURN_THRESHOLD:
                    # 偏差过大：停步原地粗修正，再重新按住 w
                    pydirectinput.keyUp('w')
                    self.logger.info(f"偏差 {abs_diff:.1f}° 过大，停步原地修正")
                    if not self.turn_to_heading(required_heading):
                        self.logger.warning("修正转向失败，移动中止")
                        return False
                    pydirectinput.keyDown('w')

                elif abs_diff > self.ANGLE_TOLERANCE:
                    # 小幅偏差：边走边转，同时按住 w 和转向键
                    # diff ≤ 180 需增大朝向 → d；diff > 180 需减小朝向 → a
                    turn_key = 'd' if diff <= 180 else 'a'
                    turn_time = min(abs_diff / self.TURN_RATE, self.SMOOTH_TURN_DURATION)
                    self.logger.info(
                        f"边走边转：按 '{turn_key}' {turn_time:.2f}s（偏差 {abs_diff:.1f}°）"
                    )
                    pydirectinput.keyDown(turn_key)
                    time.sleep(turn_time)
                    pydirectinput.keyUp(turn_key)
                else:
                    # 方向已对齐，让角色多走一段再重新检测，减少 OCR 频率
                    time.sleep(1)

        finally:
            pydirectinput.keyUp('w')

        self.logger.warning(f"移动失败：超过最大迭代次数 {max_iterations}")
        return False

    def move_to_position(self, target_x: float, target_y: float,
                         tolerance: float = 0.5, max_iterations: int = 100) -> bool:
        """移动人物到目标坐标（combat_engine 对外接口）。

        Args:
            target_x, target_y: 目标坐标
            tolerance: 到达判定半径（坐标单位），默认 0.5
            max_iterations: 最大循环次数

        Returns:
            bool: 是否成功到达目标位置
        """
        return self.move_to(target_x, target_y,
                            position_tolerance=tolerance,
                            max_iterations=max_iterations)
