"""
验证码处理模块
支持验证码识别和处理辅助

增强功能：
- 统一的验证码解决器
- 点选验证码支持
- GeeTest/极验支持
"""
import asyncio
from typing import Optional, Tuple, List, Dict, Any, Callable
from pathlib import Path
from playwright.async_api import Page
from loguru import logger
from dataclasses import dataclass
from abc import ABC, abstractmethod


@dataclass
class CaptchaResult:
    """验证码识别结果"""
    success: bool
    text: Optional[str] = None
    confidence: float = 0.0
    error: Optional[str] = None
    coordinates: Optional[List[Tuple[int, int]]] = None  # 点选验证码坐标


class CaptchaHandler:
    """
    验证码处理器
    提供验证码识别和处理辅助功能
    """
    
    def __init__(self, page: Page):
        self.page = page
        self._2captcha_api_key: Optional[str] = None
    
    def set_2captcha_key(self, api_key: str):
        """设置2Captcha API密钥"""
        self._2captcha_api_key = api_key
    
    async def save_captcha_image(
        self,
        selector: str,
        output_path: str = "captcha.png",
    ) -> bool:
        """
        保存验证码图片
        
        Args:
            selector: 验证码图片选择器
            output_path: 输出路径
            
        Returns:
            bool: 是否成功
        """
        try:
            element = self.page.locator(selector)
            await element.screenshot(path=output_path)
            logger.info(f"验证码图片已保存: {output_path}")
            return True
        except Exception as e:
            logger.error(f"保存验证码图片失败: {e}")
            return False
    
    async def recognize_with_2captcha(
        self,
        image_path: str,
        timeout: int = 120,
    ) -> CaptchaResult:
        """
        使用2Captcha识别验证码
        
        Args:
            image_path: 图片路径
            timeout: 超时时间
            
        Returns:
            CaptchaResult: 识别结果
        """
        if not self._2captcha_api_key:
            return CaptchaResult(
                success=False,
                error="未设置2Captcha API密钥",
            )
        
        try:
            import base64
            from pathlib import Path
            
            # 读取图片并转base64
            image_data = base64.b64encode(Path(image_path).read_bytes()).decode()
            
            # 提交到2Captcha
            # 这里只是示例，实际需要调用2Captcha API
            logger.info("提交验证码到2Captcha...")
            
            # 模拟识别结果
            # 实际使用时需要实现完整的API调用
            await asyncio.sleep(2)
            
            return CaptchaResult(
                success=True,
                text="demo_result",
                confidence=0.9,
            )
            
        except Exception as e:
            return CaptchaResult(
                success=False,
                error=str(e),
            )
    
    async def recognize_image(
        self,
        image_path: str,
        method: str = "local",
    ) -> CaptchaResult:
        """
        本地图片验证码识别（需要OCR库）
        
        Args:
            image_path: 图片路径
            method: 识别方法
            
        Returns:
            CaptchaResult: 识别结果
        """
        try:
            # 尝试使用pytesseract
            try:
                import pytesseract
                from PIL import Image
                
                image = Image.open(image_path)
                text = pytesseract.image_to_string(image, config='--psm 7')
                text = text.strip()
                
                if text:
                    return CaptchaResult(
                        success=True,
                        text=text,
                        confidence=0.8,
                    )
                else:
                    return CaptchaResult(
                        success=False,
                        error="OCR未能识别出文本",
                    )
                    
            except ImportError:
                return CaptchaResult(
                    success=False,
                    error="需要安装pytesseract和PIL: pip install pytesseract pillow",
                )
                
        except Exception as e:
            return CaptchaResult(
                success=False,
                error=str(e),
            )
    
    async def fill_captcha(
        self,
        input_selector: str,
        text: str,
    ) -> bool:
        """
        填写验证码
        
        Args:
            input_selector: 输入框选择器
            text: 验证码文本
            
        Returns:
            bool: 是否成功
        """
        try:
            await self.page.fill(input_selector, text)
            logger.info(f"验证码已填写: {text}")
            return True
        except Exception as e:
            logger.error(f"填写验证码失败: {e}")
            return False
    
    async def handle_recaptcha_v2(
        self,
        site_key: str,
        action: str = "submit",
    ) -> bool:
        """
        处理reCAPTCHA v2
        
        Args:
            site_key: 站点密钥
            action: 提交动作
            
        Returns:
            bool: 是否成功
        """
        try:
            # 等待reCAPTCHA加载
            await self.page.wait_for_selector('iframe[src*="recaptcha"]', timeout=10000)
            
            # 点击验证复选框
            frame = self.page.frame_locator('iframe[src*="recaptcha"]')
            await frame.locator('#recaptcha-anchor').click()
            
            logger.info("reCAPTCHA v2 已点击")
            
            # 等待验证完成
            await asyncio.sleep(3)
            
            return True
            
        except Exception as e:
            logger.error(f"处理reCAPTCHA失败: {e}")
            return False
    
    async def handle_slider_captcha(
        self,
        slider_selector: str,
        distance: int = 200,
        duration: int = 500,
    ) -> bool:
        """
        处理滑动验证码
        
        Args:
            slider_selector: 滑块选择器
            distance: 滑动距离
            duration: 滑动持续时间（毫秒）
            
        Returns:
            bool: 是否成功
        """
        try:
            slider = self.page.locator(slider_selector)
            box = await slider.bounding_box()
            
            if not box:
                return False
            
            # 获取滑块中心位置
            start_x = box["x"] + box["width"] / 2
            start_y = box["y"] + box["height"] / 2
            
            # 模拟人类滑动
            steps = 20
            for i in range(steps + 1):
                progress = i / steps
                current_x = start_x + distance * progress
                
                # 添加轻微抖动
                jitter = 2 * (0.5 - abs(progress - 0.5))
                current_y = start_y + jitter * (i % 2 * 2 - 1)
                
                await self.page.mouse.move(current_x, current_y)
                await asyncio.sleep(duration / steps / 1000)
            
            logger.info(f"滑动验证码已处理: 距离={distance}px")
            return True
            
        except Exception as e:
            logger.error(f"处理滑动验证码失败: {e}")
            return False
    
    async def detect_captcha_type(self) -> Optional[str]:
        """
        检测页面验证码类型
        
        Returns:
            Optional[str]: 验证码类型
        """
        # 检测reCAPTCHA
        if await self.page.locator('iframe[src*="recaptcha"]').count() > 0:
            return "recaptcha"
        
        # 检测滑动验证码
        if await self.page.locator('[class*="slider"], [class*="drag"]').count() > 0:
            return "slider"
        
        # 检测图片验证码
        if await self.page.locator('img[src*="captcha"]').count() > 0:
            return "image"
        
        # 检测图形验证码
        if await self.page.locator('canvas[id*="captcha"]').count() > 0:
            return "canvas"
        
        # 检测短信验证码输入框
        if await self.page.locator('input[placeholder*="验证码"], input[name*="code"]').count() > 0:
            return "sms"
        
        return None


class SMSHandler:
    """
    短信验证码处理
    提供短信验证码接收辅助
    """
    
    def __init__(self):
        self._sms_services: dict = {}
    
    def add_sms_service(self, name: str, api_url: str, api_key: str):
        """添加短信服务"""
        self._sms_services[name] = {
            "url": api_url,
            "key": api_key,
        }
    
    async def get_sms_code(
        self,
        phone: str,
        service: str = "default",
        timeout: int = 60,
    ) -> Optional[str]:
        """
        获取短信验证码
        
        Args:
            phone: 手机号
            service: 服务名称
            timeout: 超时时间
            
        Returns:
            Optional[str]: 验证码
        """
        logger.info(f"等待短信验证码: {phone}")
        
        # 这里需要实现具体的短信服务API调用
        # 以下为示例代码
        
        import re
        
        start_time = asyncio.get_event_loop().time()
        
        while asyncio.get_event_loop().time() - start_time < timeout:
            try:
                # 模拟获取短信
                # 实际使用时需要调用短信服务API
                await asyncio.sleep(5)
                
                # 模拟收到验证码
                # code = await fetch_sms_from_service(service, phone)
                
            except Exception as e:
                logger.error(f"获取短信验证码失败: {e}")
                await asyncio.sleep(5)
        
        logger.warning(f"获取短信验证码超时: {phone}")
        return None
    
    async def extract_code_from_text(self, text: str) -> Optional[str]:
        """
        从文本中提取验证码

        Args:
            text: 短信文本

        Returns:
            Optional[str]: 验证码
        """
        import re

        # 常见验证码格式
        patterns = [
            r'验证码[：:]\s*(\d{4,6})',
            r'码[：:]\s*(\d{4,6})',
            r'code[：:]\s*(\d{4,6})',
            r'(\d{4,6})',
        ]

        for pattern in patterns:
            match = re.search(pattern, text, re.IGNORECASE)
            if match:
                return match.group(1)

        return None


# ==================== 验证码解决器架构 ====================

class BaseCaptchaSolver(ABC):
    """验证码解决器基类"""

    @abstractmethod
    async def solve(self, **kwargs) -> CaptchaResult:
        """解决验证码"""
        pass

    @abstractmethod
    async def get_balance(self) -> float:
        """获取账户余额"""
        pass


class TwoCaptchaSolver(BaseCaptchaSolver):
    """2Captcha 解决器"""

    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://2captcha.com"

    async def solve(self, **kwargs) -> CaptchaResult:
        """解决验证码"""
        image_path = kwargs.get("image_path")
        site_key = kwargs.get("site_key")
        page_url = kwargs.get("page_url")

        try:
            # 实现实际的 2Captcha API 调用
            # 这里是简化版本
            logger.info("提交验证码到 2Captcha...")

            await asyncio.sleep(5)  # 模拟等待

            return CaptchaResult(
                success=True,
                text="demo_result",
                confidence=0.95,
            )

        except Exception as e:
            return CaptchaResult(success=False, error=str(e))

    async def get_balance(self) -> float:
        """获取账户余额"""
        # 实现实际的 API 调用
        return 0.0


class YescaptchaSolver(BaseCaptchaSolver):
    """YesCaptcha 解决器"""

    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://yescaptcha.com"

    async def solve(self, **kwargs) -> CaptchaResult:
        """解决验证码"""
        try:
            logger.info("提交验证码到 YesCaptcha...")
            await asyncio.sleep(3)

            return CaptchaResult(
                success=True,
                text="demo_result",
                confidence=0.90,
            )

        except Exception as e:
            return CaptchaResult(success=False, error=str(e))

    async def get_balance(self) -> float:
        return 0.0


class CaptchaSolver:
    """
    统一的验证码解决器

    自动选择合适的解决器处理不同类型的验证码。

    Example:
        solver = CaptchaSolver()
        solver.add_solver("2captcha", TwoCaptchaSolver(api_key))

        result = await solver.solve("image", image_path="captcha.png")
    """

    def __init__(self, page: Optional[Page] = None):
        self._page = page
        self._solvers: Dict[str, BaseCaptchaSolver] = {}
        self._default_solver: Optional[str] = None

    def add_solver(self, name: str, solver: BaseCaptchaSolver, set_default: bool = False) -> None:
        """
        添加解决器

        Args:
            name: 解决器名称
            solver: 解决器实例
            set_default: 是否设为默认
        """
        self._solvers[name] = solver
        if set_default or not self._default_solver:
            self._default_solver = name

    def set_default_solver(self, name: str) -> None:
        """设置默认解决器"""
        if name in self._solvers:
            self._default_solver = name
        else:
            logger.warning(f"解决器不存在: {name}")

    async def solve(self, captcha_type: str, **kwargs) -> CaptchaResult:
        """
        自动选择合适的解决器

        Args:
            captcha_type: 验证码类型 (image, recaptcha, geetest, click 等)
            **kwargs: 额外参数

        Returns:
            CaptchaResult: 解决结果
        """
        if not self._default_solver:
            return CaptchaResult(success=False, error="未配置解决器")

        solver = self._solvers.get(self._default_solver)
        if not solver:
            return CaptchaResult(success=False, error="解决器不存在")

        # 根据验证码类型调整参数
        kwargs["captcha_type"] = captcha_type

        return await solver.solve(**kwargs)

    def get_solvers(self) -> List[str]:
        """获取所有解决器"""
        return list(self._solvers.keys())

    async def get_all_balances(self) -> Dict[str, float]:
        """获取所有解决器余额"""
        balances = {}
        for name, solver in self._solvers.items():
            try:
                balances[name] = await solver.get_balance()
            except:
                balances[name] = -1
        return balances


# ==================== 点选验证码支持 ====================

class ClickCaptchaHandler:
    """
    点选验证码处理器

    处理需要点击图片特定位置的验证码。
    """

    def __init__(self, page: Page):
        self._page = page

    async def handle_click_captcha(
        self,
        image_selector: str,
        target_positions: List[Tuple[int, int]],
        delay: float = 0.3,
    ) -> bool:
        """
        处理点选验证码

        Args:
            image_selector: 验证码图片选择器
            target_positions: 需要点击的位置列表 [(x, y), ...]
            delay: 点击间隔

        Returns:
            bool: 是否成功
        """
        try:
            # 获取图片元素
            image = self._page.locator(image_selector)
            box = await image.bounding_box()

            if not box:
                logger.error("无法获取验证码图片位置")
                return False

            # 在图片上点击指定位置
            for i, (rel_x, rel_y) in enumerate(target_positions):
                # 计算实际点击位置
                click_x = box["x"] + rel_x
                click_y = box["y"] + rel_y

                await self._page.mouse.click(click_x, click_y)
                logger.debug(f"点击验证码位置 {i+1}: ({rel_x}, {rel_y})")

                if i < len(target_positions) - 1:
                    await asyncio.sleep(delay)

            logger.info(f"点选验证码已处理: {len(target_positions)} 个位置")
            return True

        except Exception as e:
            logger.error(f"处理点选验证码失败: {e}")
            return False

    async def handle_icon_captcha(
        self,
        container_selector: str,
        icon_selectors: List[str],
    ) -> bool:
        """
        处理图标点选验证码

        Args:
            container_selector: 验证码容器选择器
            icon_selectors: 需要点击的图标选择器列表

        Returns:
            bool: 是否成功
        """
        try:
            container = self._page.locator(container_selector)

            for i, selector in enumerate(icon_selectors):
                icon = container.locator(selector)
                await icon.click()
                logger.debug(f"点击图标 {i+1}: {selector}")

                if i < len(icon_selectors) - 1:
                    await asyncio.sleep(0.2)

            logger.info(f"图标点选验证码已处理: {len(icon_selectors)} 个图标")
            return True

        except Exception as e:
            logger.error(f"处理图标点选验证码失败: {e}")
            return False

    async def detect_click_positions(
        self,
        image_selector: str,
        target_text: str,
    ) -> List[Tuple[int, int]]:
        """
        检测需要点击的位置（需要 OCR 支持）

        Args:
            image_selector: 验证码图片选择器
            target_text: 目标文字

        Returns:
            List[Tuple[int, int]]: 检测到的位置列表
        """
        try:
            # 保存验证码图片
            image = self._page.locator(image_selector)
            temp_path = "temp_captcha.png"
            await image.screenshot(path=temp_path)

            # 使用 OCR 检测文字位置
            # 这里需要 OCR 库支持，例如 pytesseract 或 paddleocr
            try:
                import pytesseract
                from PIL import Image

                img = Image.open(temp_path)
                data = pytesseract.image_to_data(img, output_type=pytesseract.Output.DICT)

                positions = []
                for i, text in enumerate(data["text"]):
                    if target_text in text:
                        x = data["left"][i] + data["width"][i] // 2
                        y = data["top"][i] + data["height"][i] // 2
                        positions.append((x, y))

                return positions

            except ImportError:
                logger.warning("需要安装 pytesseract 和 PIL 来支持文字检测")
                return []

        except Exception as e:
            logger.error(f"检测点击位置失败: {e}")
            return []


# ==================== GeeTest/极验支持 ====================

class GeeTestHandler:
    """
    极验验证码处理器

    处理 GeeTest 验证码（滑动、点选等）。
    """

    def __init__(self, page: Page):
        self._page = page

    async def handle_geetest(
        self,
        gt: Optional[str] = None,
        challenge: Optional[str] = None,
        api_key: Optional[str] = None,
    ) -> bool:
        """
        处理极验验证码

        Args:
            gt: 极验 gt 参数
            challenge: 极验 challenge 参数
            api_key: 第三方打码平台 API Key

        Returns:
            bool: 是否成功
        """
        try:
            # 等待极验验证码加载
            await self._page.wait_for_selector(".geetest_panel", timeout=10000)

            # 检测验证码类型
            captcha_type = await self._detect_geetest_type()

            if captcha_type == "slide":
                return await self._handle_geetest_slide()
            elif captcha_type == "click":
                return await self._handle_geetest_click()
            else:
                logger.warning(f"未知的极验验证码类型: {captcha_type}")
                return False

        except Exception as e:
            logger.error(f"处理极验验证码失败: {e}")
            return False

    async def _detect_geetest_type(self) -> str:
        """检测极验验证码类型"""
        # 检测滑动验证码
        if await self._page.locator(".geetest_slider_button").count() > 0:
            return "slide"

        # 检测点选验证码
        if await self._page.locator(".geetest_item").count() > 0:
            return "click"

        return "unknown"

    async def _handle_geetest_slide(self) -> bool:
        """处理极验滑动验证码"""
        try:
            # 获取滑块
            slider = self._page.locator(".geetest_slider_button")
            box = await slider.bounding_box()

            if not box:
                return False

            # 获取背景图和滑块图来计算距离
            # 这里简化处理，实际需要分析图片

            # 模拟人类滑动
            start_x = box["x"] + box["width"] / 2
            start_y = box["y"] + box["height"] / 2

            # 按下鼠标
            await self._page.mouse.move(start_x, start_y)
            await self._page.mouse.down()

            # 模拟滑动轨迹
            distance = 250  # 估算的滑动距离
            steps = 30

            for i in range(steps + 1):
                progress = i / steps
                # 使用缓动函数模拟人类滑动
                ease_progress = 1 - (1 - progress) ** 3  # easeOutCubic
                current_x = start_x + distance * ease_progress

                # 添加垂直抖动
                jitter = 2 * (1 - abs(progress - 0.5) * 2)
                current_y = start_y + jitter * (i % 2 * 2 - 1)

                await self._page.mouse.move(current_x, current_y)
                await asyncio.sleep(0.01)

            # 释放鼠标
            await self._page.mouse.up()

            logger.info("极验滑动验证码已处理")
            return True

        except Exception as e:
            logger.error(f"处理极验滑动验证码失败: {e}")
            return False

    async def _handle_geetest_click(self) -> bool:
        """处理极验点选验证码"""
        try:
            # 等待验证码图片加载
            await asyncio.sleep(1)

            # 获取需要点击的位置
            # 这里简化处理，实际需要识别图片中的目标
            items = self._page.locator(".geetest_item")
            count = await items.count()

            for i in range(count):
                item = items.nth(i)
                await item.click()
                await asyncio.sleep(0.3)

            # 点击验证按钮
            verify_btn = self._page.locator(".geetest_submit")
            if await verify_btn.count() > 0:
                await verify_btn.click()

            logger.info("极验点选验证码已处理")
            return True

        except Exception as e:
            logger.error(f"处理极验点选验证码失败: {e}")
            return False

    async def get_geetest_params(self) -> Dict[str, str]:
        """
        从页面提取极验参数

        Returns:
            Dict: gt, challenge 等参数
        """
        try:
            # 尝试从页面脚本中提取参数
            gt = await self._page.evaluate(
                "() => window.geetest_gt || document.querySelector('[data-gt]')?.getAttribute('data-gt')"
            )
            challenge = await self._page.evaluate(
                "() => window.geetest_challenge || document.querySelector('[data-challenge]')?.getAttribute('data-challenge')"
            )

            return {
                "gt": gt or "",
                "challenge": challenge or "",
            }

        except Exception as e:
            logger.error(f"提取极验参数失败: {e}")
            return {}
