"""
操作录制与回放模块
记录用户操作，生成代码
"""
import json
import asyncio
from typing import Optional, List, Dict, Any, Callable
from playwright.async_api import Page
from loguru import logger
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from enum import Enum


class ActionType(Enum):
    """操作类型"""
    CLICK = "click"
    FILL = "fill"
    TYPE = "type"
    SELECT = "select"
    CHECK = "check"
    UNCHECK = "unchecked"
    NAVIGATE = "navigate"
    WAIT = "wait"
    HOVER = "hover"
    SCROLL = "scroll"
    KEYBOARD = "keyboard"
    SCREENSHOT = "screenshot"


@dataclass
class RecordedAction:
    """录制的操作"""
    action_type: ActionType
    timestamp: float
    selector: Optional[str] = None
    value: Optional[str] = None
    x: Optional[int] = None
    y: Optional[int] = None
    key: Optional[str] = None
    options: Dict[str, Any] = field(default_factory=dict)
    
    def to_dict(self) -> Dict[str, Any]:
        return {
            "action_type": self.action_type.value,
            "timestamp": self.timestamp,
            "selector": self.selector,
            "value": self.value,
            "x": self.x,
            "y": self.y,
            "key": self.key,
            "options": self.options,
        }
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "RecordedAction":
        return cls(
            action_type=ActionType(data["action_type"]),
            timestamp=data["timestamp"],
            selector=data.get("selector"),
            value=data.get("value"),
            x=data.get("x"),
            y=data.get("y"),
            key=data.get("key"),
            options=data.get("options", {}),
        )


class ActionRecorder:
    """
    操作录制器
    记录用户在页面上的操作
    """
    
    def __init__(self, page: Page):
        self.page = page
        self.actions: List[RecordedAction] = []
        self._is_recording = False
        self._start_time: Optional[float] = None
        self._listeners: List[Callable] = []
    
    async def start(self):
        """开始录制"""
        if self._is_recording:
            logger.warning("录制已在进行中")
            return
        
        self._is_recording = True
        self._start_time = datetime.now().timestamp()
        self.actions.clear()
        
        # 注入录制脚本
        await self.page.evaluate("""
            () => {
                window.__recordedActions = window.__recordedActions || [];
                window.__recording = true;
                
                // 点击事件
                document.addEventListener('click', (e) => {
                    if (!window.__recording) return;
                    
                    const element = e.target;
                    const selector = getSelector(element);
                    
                    window.__recordedActions.push({
                        action_type: 'click',
                        selector: selector,
                        timestamp: Date.now()
                    });
                }, true);
                
                // 输入事件
                document.addEventListener('input', (e) => {
                    if (!window.__recording) return;
                    
                    const element = e.target;
                    const selector = getSelector(element);
                    
                    window.__recordedActions.push({
                        action_type: 'fill',
                        selector: selector,
                        value: element.value,
                        timestamp: Date.now()
                    });
                }, true);
                
                // 选择事件
                document.addEventListener('change', (e) => {
                    if (!window.__recording) return;
                    
                    const element = e.target;
                    if (element.tagName === 'SELECT') {
                        const selector = getSelector(element);
                        window.__recordedActions.push({
                            action_type: 'select',
                            selector: selector,
                            value: element.value,
                            timestamp: Date.now()
                        });
                    }
                }, true);
                
                // 生成选择器
                function getSelector(element) {
                    if (element.id) return '#' + element.id;
                    if (element.name) return '[name="' + element.name + '"]';
                    if (element.className) {
                        const classes = element.className.split(' ').filter(c => c);
                        if (classes.length > 0) return '.' + classes[0];
                    }
                    
                    // 使用路径选择器
                    const path = [];
                    let current = element;
                    
                    while (current && current !== document.body) {
                        let selector = current.tagName.toLowerCase();
                        if (current.id) {
                            selector = '#' + current.id;
                            path.unshift(selector);
                            break;
                        }
                        
                        const siblings = current.parentElement.children;
                        let index = 1;
                        for (const sibling of siblings) {
                            if (sibling === current) break;
                            if (sibling.tagName === current.tagName) index++;
                        }
                        
                        selector += ':nth-of-type(' + index + ')';
                        path.unshift(selector);
                        current = current.parentElement;
                    }
                    
                    return path.join(' > ');
                }
                
                window.getSelector = getSelector;
            }
        """)
        
        logger.info("开始录制操作")
    
    async def stop(self) -> List[RecordedAction]:
        """停止录制"""
        if not self._is_recording:
            logger.warning("没有正在进行的录制")
            return []
        
        self._is_recording = False
        
        # 获取录制的操作
        recorded = await self.page.evaluate("""
            () => {
                window.__recording = false;
                return window.__recordedActions || [];
            }
        """)
        
        # 转换为RecordedAction
        for action_data in recorded:
            action = RecordedAction(
                action_type=ActionType(action_data["action_type"]),
                timestamp=action_data["timestamp"],
                selector=action_data.get("selector"),
                value=action_data.get("value"),
            )
            self.actions.append(action)
        
        logger.info(f"录制完成: {len(self.actions)} 个操作")
        return self.actions
    
    async def record_action(
        self,
        action_type: ActionType,
        selector: Optional[str] = None,
        value: Optional[str] = None,
    ):
        """手动记录操作"""
        action = RecordedAction(
            action_type=action_type,
            timestamp=datetime.now().timestamp(),
            selector=selector,
            value=value,
        )
        self.actions.append(action)
        logger.debug(f"记录操作: {action_type.value}, selector={selector}")
    
    def clear(self):
        """清除录制记录"""
        self.actions.clear()
        logger.info("录制记录已清除")


class ActionPlayer:
    """
    操作播放器
    回放录制的操作
    """
    
    def __init__(self, page: Page):
        self.page = page
    
    async def play(
        self,
        actions: List[RecordedAction],
        speed: float = 1.0,
        on_action: Optional[Callable[[RecordedAction], None]] = None,
    ):
        """
        播放操作
        
        Args:
            actions: 操作列表
            speed: 播放速度 (1.0 = 正常, 2.0 = 2倍速)
            on_action: 每个操作执行前的回调
        """
        logger.info(f"开始播放: {len(actions)} 个操作, 速度={speed}x")
        
        prev_timestamp = None
        
        for action in actions:
            # 计算等待时间
            if prev_timestamp is not None:
                wait_time = (action.timestamp - prev_timestamp) / speed
                if wait_time > 0:
                    await asyncio.sleep(wait_time / 1000)  # 转换为秒
            
            prev_timestamp = action.timestamp
            
            if on_action:
                on_action(action)
            
            await self._execute_action(action)
        
        logger.info("播放完成")
    
    async def _execute_action(self, action: RecordedAction):
        """执行单个操作"""
        try:
            if action.action_type == ActionType.CLICK:
                await self.page.click(action.selector)
                logger.debug(f"点击: {action.selector}")
            
            elif action.action_type == ActionType.FILL:
                await self.page.fill(action.selector, action.value)
                logger.debug(f"填充: {action.selector} = {action.value}")
            
            elif action.action_type == ActionType.TYPE:
                await self.page.type(action.selector, action.value)
                logger.debug(f"输入: {action.selector}")
            
            elif action.action_type == ActionType.SELECT:
                await self.page.select_option(action.selector, action.value)
                logger.debug(f"选择: {action.selector} = {action.value}")
            
            elif action.action_type == ActionType.CHECK:
                await self.page.check(action.selector)
                logger.debug(f"勾选: {action.selector}")
            
            elif action.action_type == ActionType.NAVIGATE:
                await self.page.goto(action.value)
                logger.debug(f"导航: {action.value}")
            
            elif action.action_type == ActionType.WAIT:
                await self.page.wait_for_timeout(float(action.value or 1000))
                logger.debug(f"等待: {action.value}ms")
            
            elif action.action_type == ActionType.HOVER:
                await self.page.hover(action.selector)
                logger.debug(f"悬停: {action.selector}")
            
            elif action.action_type == ActionType.KEYBOARD:
                await self.page.keyboard.press(action.key)
                logger.debug(f"按键: {action.key}")
            
        except Exception as e:
            logger.error(f"执行操作失败: {action.action_type.value}, error={e}")


class CodeGenerator:
    """
    代码生成器
    将录制的操作转换为代码
    """
    
    @staticmethod
    def generate_python(
        actions: List[RecordedAction],
        class_name: str = "TestRecorded",
        use_async: bool = True,
    ) -> str:
        """
        生成Python代码
        
        Args:
            actions: 操作列表
            class_name: 类名
            use_async: 是否使用异步
            
        Returns:
            str: 生成的代码
        """
        code = '''"""
自动生成的测试代码
生成时间: {timestamp}
"""
import asyncio
from playwright.async_api import async_playwright


class {class_name}:
    """录制的测试"""
    
    async def run(self):
        """执行录制操作"""
        async with async_playwright() as p:
            browser = await p.chromium.launch(headless=False)
            page = await browser.new_page()
            
            try:
{actions_code}
            finally:
                await browser.close()


if __name__ == "__main__":
    asyncio.run({class_name}().run())
'''
        
        actions_code = ""
        indent = "                "
        
        for i, action in enumerate(actions):
            if action.action_type == ActionType.CLICK:
                actions_code += f"{indent}# 步骤 {i+1}: 点击\n"
                actions_code += f"{indent}await page.click('{action.selector}')\n\n"
            
            elif action.action_type == ActionType.FILL:
                actions_code += f"{indent}# 步骤 {i+1}: 填充\n"
                actions_code += f"{indent}await page.fill('{action.selector}', '{action.value}')\n\n"
            
            elif action.action_type == ActionType.TYPE:
                actions_code += f"{indent}# 步骤 {i+1}: 输入\n"
                actions_code += f"{indent}await page.type('{action.selector}', '{action.value}')\n\n"
            
            elif action.action_type == ActionType.SELECT:
                actions_code += f"{indent}# 步骤 {i+1}: 选择\n"
                actions_code += f"{indent}await page.select_option('{action.selector}', '{action.value}')\n\n"
            
            elif action.action_type == ActionType.NAVIGATE:
                actions_code += f"{indent}# 步骤 {i+1}: 导航\n"
                actions_code += f"{indent}await page.goto('{action.value}')\n\n"
            
            elif action.action_type == ActionType.WAIT:
                actions_code += f"{indent}# 步骤 {i+1}: 等待\n"
                actions_code += f"{indent}await page.wait_for_timeout({action.value})\n\n"
        
        return code.format(
            timestamp=datetime.now().isoformat(),
            class_name=class_name,
            actions_code=actions_code,
        )
    
    @staticmethod
    def generate_playwright_test(
        actions: List[RecordedAction],
        test_name: str = "recorded_test",
    ) -> str:
        """
        生成Playwright测试代码
        
        Args:
            actions: 操作列表
            test_name: 测试名
            
        Returns:
            str: 生成的代码
        """
        code = '''/*
自动生成的Playwright测试
生成时间: {timestamp}
*/
import {{ test, expect }} from '@playwright/test';

test('{test_name}', async ({{ page }}) => {{
{actions_code}
}});
'''
        
        actions_code = ""
        indent = "  "
        
        for i, action in enumerate(actions):
            if action.action_type == ActionType.CLICK:
                actions_code += f"{indent}// 步骤 {i+1}: 点击\n"
                actions_code += f"{indent}await page.click('{action.selector}');\n\n"
            
            elif action.action_type == ActionType.FILL:
                actions_code += f"{indent}// 步骤 {i+1}: 填充\n"
                actions_code += f"{indent}await page.fill('{action.selector}', '{action.value}');\n\n"
            
            elif action.action_type == ActionType.NAVIGATE:
                actions_code += f"{indent}// 步骤 {i+1}: 导航\n"
                actions_code += f"{indent}await page.goto('{action.value}');\n\n"
        
        return code.format(
            timestamp=datetime.now().isoformat(),
            test_name=test_name,
            actions_code=actions_code,
        )


class RecordingSession:
    """
    录制会话
    管理录制、保存、加载
    """
    
    def __init__(self, page: Page):
        self.page = page
        self.recorder = ActionRecorder(page)
        self.player = ActionPlayer(page)
        self.generator = CodeGenerator()
    
    async def start_recording(self):
        """开始录制"""
        await self.recorder.start()
    
    async def stop_recording(self) -> List[RecordedAction]:
        """停止录制"""
        return await self.recorder.stop()
    
    async def replay(self, speed: float = 1.0):
        """回放录制"""
        await self.player.play(self.recorder.actions, speed)
    
    def save(self, filepath: str):
        """保存录制"""
        data = {
            "actions": [a.to_dict() for a in self.recorder.actions],
            "saved_at": datetime.now().isoformat(),
        }
        
        Path(filepath).parent.mkdir(parents=True, exist_ok=True)
        with open(filepath, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
        
        logger.info(f"录制已保存: {filepath}")
    
    def load(self, filepath: str) -> List[RecordedAction]:
        """加载录制"""
        with open(filepath, 'r', encoding='utf-8') as f:
            data = json.load(f)
        
        self.recorder.actions = [
            RecordedAction.from_dict(a) for a in data["actions"]
        ]
        
        logger.info(f"录制已加载: {filepath}, {len(self.recorder.actions)} 个操作")
        return self.recorder.actions
    
    def export_python(self, filepath: str, class_name: str = "TestRecorded"):
        """导出为Python代码"""
        code = self.generator.generate_python(
            self.recorder.actions,
            class_name,
        )
        
        Path(filepath).write_text(code, encoding='utf-8')
        logger.info(f"Python代码已导出: {filepath}")
    
    def export_playwright_test(self, filepath: str, test_name: str = "recorded_test"):
        """导出为Playwright测试"""
        code = self.generator.generate_playwright_test(
            self.recorder.actions,
            test_name,
        )
        
        Path(filepath).write_text(code, encoding='utf-8')
        logger.info(f"Playwright测试已导出: {filepath}")