"""
自动录制器
支持操作录制、代码回放
"""
import json
import asyncio
from dataclasses import dataclass, field
from typing import Optional, List, Dict, Any
from datetime import datetime
from pathlib import Path
from playwright.async_api import Page
from loguru import logger


@dataclass
class AutoRecorderConfig:
    """自动录制配置"""
    record_clicks: bool = True
    record_input: bool = True
    record_navigation: bool = True
    record_scrolls: bool = False
    record_mouse_movement: bool = False
    record_keypress: bool = True
    ignore_selectors: List[str] = field(default_factory=list)
    max_actions: int = 1000


@dataclass
class RecordedAction:
    """录制的动作"""
    type: str  # click, input, navigate, scroll, keypress
    timestamp: str
    selector: Optional[str] = None
    value: Optional[str] = None
    tag: Optional[str] = None
    text: Optional[str] = None
    url: Optional[str] = None
    x: Optional[int] = None
    y: Optional[int] = None
    key: Optional[str] = None
    modifiers: List[str] = field(default_factory=list)
    metadata: Dict[str, Any] = field(default_factory=dict)


@dataclass
class RecordingSession:
    """录制会话"""
    id: str
    start_time: str
    end_time: Optional[str] = None
    url: Optional[str] = None
    actions: List[RecordedAction] = field(default_factory=list)

    @property
    def action_count(self) -> int:
        return len(self.actions)

    @property
    def duration_seconds(self) -> float:
        if not self.end_time:
            return 0.0
        start = datetime.fromisoformat(self.start_time)
        end = datetime.fromisoformat(self.end_time)
        return (end - start).total_seconds()


class AutoRecorder:
    """
    自动录制器
    录制用户操作并生成代码
    """

    def __init__(
        self,
        page: Page,
        config: Optional[AutoRecorderConfig] = None,
    ):
        self.page = page
        self.config = config or AutoRecorderConfig()
        self._session: Optional[RecordingSession] = None
        self._is_recording = False

    # ==================== 录制控制 ====================

    async def start(self, url: Optional[str] = None):
        """开始录制"""
        if self._is_recording:
            logger.warning("录制已在进行中")
            return

        import uuid
        self._session = RecordingSession(
            id=str(uuid.uuid4())[:8],
            start_time=datetime.now().isoformat(),
            url=url or self.page.url,
        )
        self._is_recording = True
        await self._inject_recorder()
        await self._setup_listeners()
        logger.info(f"开始录制: {self._session.id}")

    async def stop(self) -> RecordingSession:
        """停止录制"""
        if not self._is_recording or not self._session:
            raise RuntimeError("没有正在进行的录制")

        self._is_recording = False
        self._session.end_time = datetime.now().isoformat()
        await self._remove_listeners()
        logger.info(f"录制结束: {self._session.id}, 动作数: {self._session.action_count}")
        return self._session

    async def pause(self):
        """暂停录制"""
        self._is_recording = False

    async def resume(self):
        """恢复录制"""
        self._is_recording = True

    # ==================== 内部方法 ====================

    async def _inject_recorder(self):
        """注入录制脚本"""
        ignore_selectors = json.dumps(self.config.ignore_selectors)
        await self.page.evaluate(f"""
            window.__recorderActions = window.__recorderActions || [];
            window.__recorderIgnore = {ignore_selectors};

            function getSelector(element) {{
                if (element.id) return '#' + element.id;
                if (element.dataset && element.dataset.testid) {{
                    return '[data-testid="' + element.dataset.testid + '"]';
                }}
                let selector = element.tagName.toLowerCase();
                if (element.className && typeof element.className === 'string') {{
                    const classes = element.className.split(' ').filter(c => c && !c.includes(':'));
                    if (classes.length) selector += '.' + classes.slice(0, 2).join('.');
                }}
                return selector;
            }}

            function shouldIgnore(element) {{
                const ignore = window.__recorderIgnore || [];
                for (const sel of ignore) {{
                    if (element.matches && element.matches(sel)) return true;
                }}
                return false;
            }}

            function recordAction(action) {{
                action.timestamp = new Date().toISOString();
                window.__recorderActions.push(action);
                if (window.__recorderActions.length > {self.config.max_actions}) {{
                    window.__recorderActions.shift();
                }}
            }}

            window.__recorderGetSelector = getSelector;
            window.__recorderRecord = recordAction;
            window.__recorderShouldIgnore = shouldIgnore;
        """)

    async def _setup_listeners(self):
        """设置事件监听"""
        if self.config.record_clicks:
            await self.page.evaluate("""
                window.__clickHandler = (e) => {
                    if (!window.__recordingActive) return;
                    if (window.__recorderShouldIgnore(e.target)) return;
                    window.__recorderRecord({
                        type: 'click',
                        selector: window.__recorderGetSelector(e.target),
                        tag: e.target.tagName.toLowerCase(),
                        text: e.target.innerText?.substring(0, 50),
                        x: e.clientX,
                        y: e.clientY,
                    });
                };
                document.addEventListener('click', window.__clickHandler, true);
            """)

        if self.config.record_input:
            await self.page.evaluate("""
                window.__inputHandler = (e) => {
                    if (!window.__recordingActive) return;
                    if (window.__recorderShouldIgnore(e.target)) return;
                    const element = e.target;
                    const type = element.type || 'text';
                    const value = type === 'password' ? '***' : element.value;
                    window.__recorderRecord({
                        type: 'input',
                        selector: window.__recorderGetSelector(element),
                        value: value,
                        tag: element.tagName.toLowerCase(),
                        inputType: type,
                    });
                };
                document.addEventListener('input', window.__inputHandler, true);
            """)

        if self.config.record_navigation:
            self.page.on("framenavigated", self._on_navigation)

        await self.page.evaluate("window.__recordingActive = true;")

    async def _remove_listeners(self):
        """移除事件监听"""
        try:
            await self.page.evaluate("""
                window.__recordingActive = false;
                if (window.__clickHandler) {
                    document.removeEventListener('click', window.__clickHandler, true);
                }
                if (window.__inputHandler) {
                    document.removeEventListener('input', window.__inputHandler, true);
                }
            """)
            self.page.remove_listener("framenavigated", self._on_navigation)
        except Exception as e:
            logger.warning(f"移除监听器失败: {e}")

    def _on_navigation(self, frame):
        """导航事件处理"""
        if not self._is_recording or not self._session:
            return
        action = RecordedAction(
            type="navigate",
            timestamp=datetime.now().isoformat(),
            url=frame.url,
        )
        self._session.actions.append(action)

    # ==================== 导出功能 ====================

    async def collect_actions(self) -> List[RecordedAction]:
        """收集录制的动作"""
        if not self._session:
            return []
        actions_data = await self.page.evaluate("window.__recorderActions || []")
        for action_data in actions_data:
            action = RecordedAction(
                type=action_data.get("type", "unknown"),
                timestamp=action_data.get("timestamp", datetime.now().isoformat()),
                selector=action_data.get("selector"),
                value=action_data.get("value"),
                tag=action_data.get("tag"),
                text=action_data.get("text"),
            )
            exists = any(
                a.timestamp == action.timestamp and a.type == action.type
                for a in self._session.actions
            )
            if not exists:
                self._session.actions.append(action)
        return self._session.actions

    async def export_json(self, file_path: str):
        """导出为 JSON"""
        await self.collect_actions()
        if not self._session:
            raise RuntimeError("没有录制会话")

        data = {
            "id": self._session.id,
            "start_time": self._session.start_time,
            "end_time": self._session.end_time,
            "url": self._session.url,
            "actions": [
                {"type": a.type, "timestamp": a.timestamp, "selector": a.selector, "value": a.value}
                for a in self._session.actions
            ],
        }
        Path(file_path).parent.mkdir(parents=True, exist_ok=True)
        with open(file_path, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
        logger.info(f"录制已导出: {file_path}")

    async def export_python_code(self, file_path: str):
        """导出为 Python 代码"""
        await self.collect_actions()
        if not self._session:
            raise RuntimeError("没有录制会话")

        lines = [
            '"""自动生成的测试代码"""',
            'import asyncio',
            'from playwright_ez import EasyBrowser',
            '',
            'async def recorded_test():',
            '    async with EasyBrowser() as browser:',
        ]
        for action in self._session.actions:
            if action.type == "navigate":
                lines.append(f'        await browser.goto("{action.url}")')
            elif action.type == "click" and action.selector:
                lines.append(f'        await browser.click("{action.selector}")')
            elif action.type == "input" and action.selector and action.value:
                escaped = action.value.replace('"', '\\"')
                lines.append(f'        await browser.type_text("{action.selector}", "{escaped}")')

        lines.extend(['', 'if __name__ == "__main__":', '    asyncio.run(recorded_test())'])

        Path(file_path).parent.mkdir(parents=True, exist_ok=True)
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write('\n'.join(lines))
        logger.info(f"Python 代码已导出: {file_path}")


def create_auto_recorder(page: Page, config: Optional[AutoRecorderConfig] = None) -> AutoRecorder:
    """创建自动录制器实例"""
    return AutoRecorder(page, config)
