# from DrissionPage import ChromiumPage
# # 假设上述代码存为 human_keyboard.py
# from human_keyboard import HumanKeyboard, Key

# page = ChromiumPage()
# page.get('https://www.baidu.com')

# # 初始化高度拟人化键盘
# keyboard = HumanKeyboard(page)

# # 1. 点击搜索框以获得输入焦点
# ele = page.ele('#kw')
# ele.click()

# # 2. 以极度拟人化的方式输入文本（有概率打错字又自己倒退回删并重新打）
# # 因为开了 humanize，你会看到打字速度变化、可能突然思考停顿等效果。
# keyboard.type_text("Hello World, let's test humanized typing!!!", humanize=True)

# # 3. 按下回车键
# keyboard.press(Key.ENTER)

# # 4. 执行快捷键 (如 Ctrl + A 全选)
# keyboard.hotkey(Key.CONTROL, Key.A)

import logging
import random
import time
import warnings
from dataclasses import dataclass
from enum import Enum
from typing import Optional, Tuple, List

# 引入 DrissionPage 的基础类以进行类型提示
from DrissionPage import ChromiumPage

logger = logging.getLogger(__name__)

# ==========================================
# 常量、枚举与键盘布局配置
# ==========================================

DEFAULT_TYPO_PROBABILITY = 0.02

# 模拟错别字时需要的 QWERTY 键盘相邻按键映射表
QWERTY_NEIGHBORS = {
    'q': ['w', 'a', 's'], 'w': ['q', 'e', 'a', 's', 'd'], 'e': ['w', 'r', 's', 'd', 'f'],
    'r': ['e', 't', 'd', 'f', 'g'], 't': ['r', 'y', 'f', 'g', 'h'], 'y': ['t', 'u', 'g', 'h', 'j'],
    'u': ['y', 'i', 'h', 'j', 'k'], 'i': ['u', 'o', 'j', 'k', 'l'], 'o': ['i', 'p', 'k', 'l'],
    'p': ['o', 'l'], 'a': ['q', 'w', 's', 'z', 'x'], 's': ['q', 'w', 'e', 'a', 'd', 'z', 'x', 'c'],
    'd': ['w', 'e', 'r', 's', 'f', 'x', 'c', 'v'], 'f': ['e', 'r', 't', 'd', 'g', 'c', 'v', 'b'],
    'g': ['r', 't', 'y', 'f', 'h', 'v', 'b', 'n'], 'h': ['t', 'y', 'u', 'g', 'j', 'b', 'n', 'm'],
    'j': ['y', 'u', 'i', 'h', 'k', 'n', 'm'], 'k': ['u', 'i', 'o', 'j', 'l', 'm'],
    'l': ['i', 'o', 'p', 'k'], 'z': ['a', 's', 'x'], 'x': ['a', 's', 'd', 'z', 'c'],
    'c': ['s', 'd', 'f', 'x', 'v'], 'v': ['d', 'f', 'g', 'c', 'b'],
    'b': ['f', 'g', 'h', 'v', 'n'], 'n': ['g', 'h', 'j', 'b', 'm'],
    'm': ['h', 'j', 'k', 'n']
}


class TypoType(Enum):
    """错字类型"""
    ADJACENT = "adjacent"          # 按到相邻键
    TRANSPOSE = "transpose"        # 字母顺序颠倒
    DOUBLE = "double"              # 重复按键
    SKIP = "skip"                  # 漏按
    MISSED_SPACE = "missed_space"  # 漏按空格


class Key(Enum):
    """
    底层 CDP 需要的特殊按键定义。
    格式: (key_name, windows_virtual_key_code)
    """
    BACKSPACE = ("Backspace", 8)
    TAB = ("Tab", 9)
    ENTER = ("Enter", 13)
    SHIFT = ("Shift", 16)
    CONTROL = ("Control", 17)
    ALT = ("Alt", 18)
    ESCAPE = ("Escape", 27)
    SPACE = (" ", 32)
    META = ("Meta", 91)  # Win / Cmd 键


@dataclass(frozen=True)
class TypoResult:
    """错字生成结果"""
    typo_type: TypoType
    wrong_char: str = ''


@dataclass(frozen=True)
class TimingConfig:
    """高仿真打字节奏时间配置 (单位：秒)"""
    keystroke_min: float = 0.03
    keystroke_max: float = 0.12
    punctuation_min: float = 0.08
    punctuation_max: float = 0.18
    thinking_probability: float = 0.02
    thinking_min: float = 0.3
    thinking_max: float = 0.7
    distraction_probability: float = 0.005
    distraction_min: float = 0.5
    distraction_max: float = 1.2
    mistake_realize_min: float = 0.1
    mistake_realize_max: float = 0.25
    after_correction_min: float = 0.03
    after_correction_max: float = 0.08
    double_press_min: float = 0.02
    double_press_max: float = 0.05
    hesitation_min: float = 0.15
    hesitation_max: float = 0.3


@dataclass(frozen=True)
class TypoConfig:
    """各类错字发生的权重比例"""
    adjacent_weight: float = 0.55
    transpose_weight: float = 0.20
    double_weight: float = 0.12
    skip_weight: float = 0.08
    missed_space_weight: float = 0.05


# ==========================================
# 核心控制类
# ==========================================

class HumanKeyboard:
    """
    DrissionPage 的高仿真人类键盘控制器。
    提供：
    - 公共按键操作（press, down, up, hotkey）
    - 私有文本输入，包含高度拟人的错别字产生、自我纠正、思考停顿与节奏变化。
    """

    PAUSE_CHARS = frozenset(' .,!?;:\n')

    def __init__(
        self,
        page: ChromiumPage,
        timing: Optional[TimingConfig] = None,
        typo_config: Optional[TypoConfig] = None,
    ):
        """
        初始化键盘控制器。

        :param page: DrissionPage 的 ChromiumPage 或 ChromiumTab 实例
        :param timing: 节奏时间配置
        :param typo_config: 错别字权重配置
        """
        self._page = page
        self._timing = timing or TimingConfig()
        self._typo_config = typo_config or TypoConfig()

    def press(
        self,
        key: Key,
        modifiers: Optional[int] = None,
        interval: float = 0.1,
    ):
        """
        按下一个键并释放。
        
        :param key: 要按的键 (来自 Key enum)
        :param modifiers: 组合键掩码 (Alt=1, Ctrl=2, Meta=4, Shift=8)
        :param interval: 按下和释放之间的延迟
        """
        logger.debug(f'Pressing key: {key.name} with modifiers: {modifiers}')
        self.down(key, modifiers)
        time.sleep(interval)
        self.up(key)

    def down(self, key: Key, modifiers: Optional[int] = None):
        """按下按键（不释放）"""
        key_name, code = key.value
        logger.debug(f'Key down: {key_name}')
        
        params = {
            "type": "keyDown",
            "key": key_name,
            "windowsVirtualKeyCode": code,
            "nativeVirtualKeyCode": code,
        }
        if modifiers is not None:
            params["modifiers"] = modifiers
            
        self._page.run_cdp("Input.dispatchKeyEvent", **params)

    def up(self, key: Key):
        """释放按键"""
        key_name, code = key.value
        logger.debug(f'Key up: {key_name}')
        
        self._page.run_cdp(
            "Input.dispatchKeyEvent",
            type="keyUp",
            key=key_name,
            windowsVirtualKeyCode=code,
            nativeVirtualKeyCode=code,
        )

    def hotkey(self, key1: Key, key2: Key, key3: Optional[Key] = None):
        """
        执行组合快捷键（最多支持3个键）。
        例如：keyboard.hotkey(Key.CONTROL, Key.C)
        """
        logger.debug(f'Hotkey: {key1.name} + {key2.name}' + (f' + {key3.name}' if key3 else ''))
        keys = [key1, key2]
        if key3 is not None:
            keys.append(key3)

        modifiers, non_modifiers = self._split_modifiers_and_keys(keys)
        modifier_value = self._calculate_modifier_value(modifiers)

        for key in non_modifiers:
            self.down(key, modifiers=modifier_value)
            time.sleep(0.05)

        time.sleep(0.1)

        for key in reversed(non_modifiers):
            self.up(key)
            time.sleep(0.05)

    def type_text(
        self,
        text: str,
        humanize: bool = False,
        interval: Optional[float] = None,
    ):
        """
        逐字输入文本。

        :param text: 要输入的文本
        :param humanize: 是否开启拟人化（变速、犯错后退格重打、思考停顿）
        :param interval: (已弃用) 字符输入间隔
        """
        if interval is not None:
            warnings.warn(
                '"interval" 参数已被弃用。请使用 "humanize=True" 开启真实的人类打字模拟。',
                DeprecationWarning,
                stacklevel=2,
            )

        if humanize:
            self._type_text_humanized(text)
            return

        for current_char in text:
            self._type_char(current_char)
            time.sleep(0.05)

    def _type_text_humanized(self, text: str):
        """高拟人化文本输入核心逻辑"""
        char_index = 0
        while char_index < len(text):
            current_char = text[char_index]
            next_char = text[char_index + 1] if char_index + 1 < len(text) else None

            # 判断是否打错字并执行错字修补逻辑，返回 True 表示下个字应跳过
            should_skip_next = self._process_char_with_typo(current_char, next_char)

            if should_skip_next:
                char_index += 1

            self._apply_realistic_delay(current_char)
            char_index += 1

    def _type_char(self, char: str):
        """通过底层 CDP 输入单个普通字符"""
        # 下拉字符事件
        self._page.run_cdp(
            "Input.dispatchKeyEvent",
            type="keyDown",
            key=char,
            text=char,
            unmodifiedText=char
        )
        # 抬起字符事件
        self._page.run_cdp(
            "Input.dispatchKeyEvent",
            type="keyUp",
            key=char
        )

    def _type_backspace(self):
        """输入退格键（用来模拟人打错字回删）"""
        self.press(Key.BACKSPACE)

    def _process_char_with_typo(
        self,
        current_char: str,
        next_char: Optional[str],
    ) -> bool:
        """处理错别字产生与修复。返回 True 表示下个字符需要跳过处理"""
        if not self._should_make_typo():
            self._type_char(current_char)
            return False

        typo = self._generate_typo(current_char, next_char)
        return self._handle_typo(current_char, next_char, typo)

    def _handle_typo(
        self,
        current_char: str,
        next_char: Optional[str],
        typo: TypoResult,
    ) -> bool:
        """根据错别字类型执行具体的打错和修正行为"""
        if typo.typo_type == TypoType.ADJACENT:
            self._do_adjacent_typo(current_char, typo.wrong_char)
            return False

        if typo.typo_type == TypoType.TRANSPOSE and next_char:
            self._do_transpose_typo(current_char, next_char)
            return True

        if typo.typo_type == TypoType.DOUBLE:
            self._do_double_typo(current_char)
            return False

        if typo.typo_type == TypoType.SKIP:
            self._do_skip_typo(current_char)
            return False

        if typo.typo_type == TypoType.MISSED_SPACE and current_char == ' ' and next_char:
            self._do_missed_space_typo(current_char, next_char)
            return True

        self._type_char(current_char)
        return False

    def _do_adjacent_typo(self, correct_char: str, wrong_char: str):
        """打错相邻键 -> 停顿 -> 退格删除 -> 补上正确的"""
        timing = self._timing
        self._type_char(wrong_char)
        time.sleep(random.uniform(timing.mistake_realize_min, timing.mistake_realize_max))
        self._type_backspace()
        time.sleep(random.uniform(timing.after_correction_min, timing.after_correction_max))
        self._type_char(correct_char)

    def _do_transpose_typo(self, current_char: str, next_char: str):
        """字母顺序打反 -> 停顿 -> 删两格 -> 重新打正确的顺序"""
        timing = self._timing
        self._type_char(next_char)
        time.sleep(random.uniform(timing.keystroke_min, timing.keystroke_max))
        self._type_char(current_char)

        time.sleep(random.uniform(timing.mistake_realize_min, timing.mistake_realize_max))
        self._type_backspace()
        self._type_backspace()
        time.sleep(random.uniform(timing.after_correction_min, timing.after_correction_max))

        self._type_char(current_char)
        time.sleep(random.uniform(timing.keystroke_min, timing.keystroke_max))
        self._type_char(next_char)

    def _do_double_typo(self, current_char: str):
        """不小心连按两次 -> 停顿 -> 退格删掉多余的一个"""
        timing = self._timing
        self._type_char(current_char)
        time.sleep(random.uniform(timing.double_press_min, timing.double_press_max))
        self._type_char(current_char)
        time.sleep(random.uniform(timing.mistake_realize_min, timing.mistake_realize_max))
        self._type_backspace()

    def _do_skip_typo(self, current_char: str):
        """大脑卡壳漏打：犹豫停顿 -> 接着打正常的"""
        timing = self._timing
        time.sleep(random.uniform(timing.hesitation_min, timing.hesitation_max))
        self._type_char(current_char)

    def _do_missed_space_typo(self, space_char: str, next_char: str):
        """漏敲空格 -> 接着敲了下一个字 -> 发现错误 -> 退格删掉 -> 补空格 -> 重打下个字"""
        timing = self._timing
        self._type_char(next_char)
        time.sleep(random.uniform(timing.mistake_realize_min, timing.mistake_realize_max))
        self._type_backspace()
        time.sleep(random.uniform(timing.after_correction_min, timing.after_correction_max))
        self._type_char(space_char)
        time.sleep(random.uniform(timing.after_correction_min, timing.after_correction_max))
        self._type_char(next_char)

    def _apply_realistic_delay(self, typed_char: str):
        """根据输入的不同字符和人的状态，加入随机延迟"""
        timing = self._timing
        delay = random.uniform(timing.keystroke_min, timing.keystroke_max)

        # 遇到标点符号，延迟加长
        if typed_char in self.PAUSE_CHARS:
            delay += random.uniform(timing.punctuation_min, timing.punctuation_max)

        # 模拟突然思考
        if random.random() < timing.thinking_probability:
            delay += random.uniform(timing.thinking_min, timing.thinking_max)

        # 模拟被打断分心
        if random.random() < timing.distraction_probability:
            delay += random.uniform(timing.distraction_min, timing.distraction_max)

        time.sleep(delay)

    @staticmethod
    def _should_make_typo() -> bool:
        """决定此时是否发生手误"""
        return random.random() < DEFAULT_TYPO_PROBABILITY

    def _generate_typo(self, current_char: str, next_char: Optional[str]) -> TypoResult:
        """生成一个具体的错误结果"""
        typo_type = self._select_typo_type()
        return self._create_typo(typo_type, current_char, next_char)

    def _select_typo_type(self) -> TypoType:
        """通过权重随机挑选错误类型"""
        config = self._typo_config
        typo_types = [
            TypoType.ADJACENT,
            TypoType.TRANSPOSE,
            TypoType.DOUBLE,
            TypoType.SKIP,
            TypoType.MISSED_SPACE,
        ]
        typo_weights = [
            config.adjacent_weight,
            config.transpose_weight,
            config.double_weight,
            config.skip_weight,
            config.missed_space_weight,
        ]
        return random.choices(typo_types, weights=typo_weights, k=1)[0]

    def _create_typo(
        self,
        typo_type: TypoType,
        current_char: str,
        next_char: Optional[str],
    ) -> TypoResult:
        """构建错字返回结果"""
        if typo_type == TypoType.ADJACENT:
            return self._create_adjacent_typo(current_char)
        elif typo_type == TypoType.TRANSPOSE:
            return self._create_transpose_typo(current_char, next_char)
        elif typo_type == TypoType.MISSED_SPACE:
            return self._create_missed_space_typo(current_char)
        elif typo_type == TypoType.DOUBLE:
            return TypoResult(typo_type=TypoType.DOUBLE, wrong_char=current_char)
        else:
            return TypoResult(typo_type=TypoType.SKIP)

    def _create_transpose_typo(self, current_char: str, next_char: Optional[str]) -> TypoResult:
        """顺序颠倒错误（如果下个不是字母则降级为按错相邻键）"""
        if next_char and next_char.isalpha():
            return TypoResult(typo_type=TypoType.TRANSPOSE, wrong_char=next_char)
        return self._create_adjacent_typo(current_char)

    def _create_missed_space_typo(self, current_char: str) -> TypoResult:
        """漏打空格错误（如果当前本不是空格则降级为按错相邻键）"""
        if current_char == ' ':
            return TypoResult(typo_type=TypoType.MISSED_SPACE)
        return self._create_adjacent_typo(current_char)

    @staticmethod
    def _create_adjacent_typo(original_char: str) -> TypoResult:
        """生成按错相邻键的错误"""
        lowercase_char = original_char.lower()

        # 如果字典中没有对应相邻键，降级成连按两下
        if lowercase_char not in QWERTY_NEIGHBORS:
            return TypoResult(typo_type=TypoType.DOUBLE, wrong_char=original_char)

        adjacent_char = random.choice(QWERTY_NEIGHBORS[lowercase_char])

        if original_char.isupper():
            adjacent_char = adjacent_char.upper()

        return TypoResult(typo_type=TypoType.ADJACENT, wrong_char=adjacent_char)

    @staticmethod
    def _split_modifiers_and_keys(keys: List[Key]) -> Tuple[List[Key], List[Key]]:
        """分离修饰键(Ctrl/Shift等)和普通键"""
        modifier_keys = {Key.CONTROL, Key.SHIFT, Key.ALT, Key.META}
        modifiers = [key for key in keys if key in modifier_keys]
        non_modifiers = [key for key in keys if key not in modifier_keys]
        return modifiers, non_modifiers

    @staticmethod
    def _calculate_modifier_value(modifiers: List[Key]) -> Optional[int]:
        """计算 CDP 事件所需的 modifiers 掩码值"""
        if not modifiers:
            return None

        modifier_map = {
            Key.ALT: 1,
            Key.CONTROL: 2,
            Key.META: 4,
            Key.SHIFT: 8,
        }
        value = sum(modifier_map.get(mod, 0) for mod in modifiers)
        return value if value > 0 else None