"""
规则引擎
使用启发式规则进行选择器建议、错误诊断等
"""
import re
from datetime import datetime
from typing import Optional, List, Dict

from playwright.async_api import Page
from loguru import logger

from .base import BaseEngine
from ..types import SelectorSuggestion, ErrorDiagnosis, CodeSuggestion, AIConfig
from ..finder import SmartElementFinder
from ..analyzer import PageAnalyzer


class RuleEngine(BaseEngine):
    """规则引擎：使用启发式规则进行 AI 辅助"""

    def __init__(self, page: Page, config: Optional[AIConfig] = None):
        self.page = page
        self.config = config or AIConfig()
        self._finder = SmartElementFinder(page)
        self._analyzer = PageAnalyzer(page)
        self._error_history: List[Dict] = []

    async def suggest_selectors(
        self,
        description: str,
        element_type: Optional[str] = None,
    ) -> List[SelectorSuggestion]:
        """根据描述建议选择器"""
        suggestions = []

        # 分析描述中的关键词
        keywords = self._extract_keywords_from_description(description)

        # 根据元素类型生成候选选择器
        candidates = await self._generate_candidate_selectors(keywords, element_type)

        # 验证并排序
        for selector, strategy in candidates:
            confidence = await self._validate_selector(selector)
            if confidence > 0:
                suggestions.append(SelectorSuggestion(
                    selector=selector,
                    confidence=confidence,
                    strategy=strategy,
                    element_type=element_type or "unknown",
                    description=f"通过 {strategy} 策略找到",
                ))

        # 按置信度排序
        suggestions.sort(key=lambda x: x.confidence, reverse=True)
        return suggestions[:self.config.max_suggestions]

    def _extract_keywords_from_description(self, description: str) -> Dict[str, str]:
        """从描述中提取关键词"""
        keywords = {
            'text': None,
            'placeholder': None,
            'label': None,
            'id': None,
            'class': None,
            'aria': None,
        }

        # 提取文本内容
        text_patterns = [
            r'文本["\']?[:：]["\']?(\S+)',
            r'包含["\']?(\S+)["\']?',
            r'显示["\']?(\S+)["\']?',
        ]
        for pattern in text_patterns:
            match = re.search(pattern, description)
            if match:
                keywords['text'] = match.group(1)
                break

        # 如果没有匹配到，把整个描述当作文本
        if not keywords['text']:
            keywords['text'] = description.strip()

        return keywords

    async def _generate_candidate_selectors(
        self,
        keywords: Dict[str, str],
        element_type: Optional[str],
    ) -> List[tuple]:
        """生成候选选择器"""
        candidates = []

        text = keywords.get('text')
        if not text:
            return candidates

        # 根据元素类型生成
        if element_type == 'button' or '按钮' in text or 'button' in text.lower():
            candidates.extend([
                (f"button:has-text('{text}')", 'text-button'),
                (f"input[type='submit'][value*='{text}']", 'submit-input'),
                (f"input[type='button'][value*='{text}']", 'button-input'),
                (f"[role='button']:has-text('{text}')", 'role-button'),
                (f"a:has-text('{text}')", 'link-button'),
                (f"[onclick]:has-text('{text}')", 'onclick'),
            ])

        elif element_type == 'input' or '输入' in text:
            candidates.extend([
                (f"input[placeholder*='{text}']", 'placeholder'),
                (f"input[name*='{text.lower().replace(' ', '_')}']", 'name'),
                (f"textarea[placeholder*='{text}']", 'textarea-placeholder'),
                (f"[aria-label*='{text}']", 'aria-label'),
            ])

        elif element_type == 'link' or '链接' in text:
            candidates.extend([
                (f"a:has-text('{text}')", 'link-text'),
                (f"a[href*='{text.lower()}']", 'link-href'),
            ])

        else:
            # 通用策略
            candidates.extend([
                (f"text={text}", 'playwright-text'),
                (f":text('{text}')", 'text-is'),
                (f":text-is('{text}')", 'text-exact'),
                (f"[aria-label*='{text}']", 'aria-label'),
                (f"[title*='{text}']", 'title'),
            ])

        return candidates

    async def _validate_selector(self, selector: str) -> float:
        """验证选择器并返回置信度"""
        try:
            locator = self.page.locator(selector)
            count = await locator.count()

            if count == 0:
                return 0.0
            elif count == 1:
                return 1.0
            else:
                # 多个匹配，置信度降低
                return max(0.5, 1.0 - (count - 1) * 0.1)

        except Exception:
            return 0.0

    async def diagnose_error(
        self,
        error: Exception,
        context: Optional[dict] = None,
    ) -> ErrorDiagnosis:
        """诊断错误并提供解决方案"""
        error_message = str(error)
        error_type = self._classify_error(error_message)

        causes = []
        solutions = []
        related_elements = []

        # 根据错误类型分析
        if error_type == 'selector_not_found':
            causes = [
                "选择器不正确或元素不存在",
                "页面未完全加载",
                "元素在 iframe 中",
                "元素被动态生成",
            ]
            solutions = [
                "检查选择器拼写",
                "添加等待条件: await page.wait_for_selector()",
                "检查是否需要切换 iframe",
                "使用更灵活的选择器策略",
            ]
            related_elements = await self._find_similar_elements(context)

        elif error_type == 'timeout':
            causes = [
                "页面加载过慢",
                "网络问题",
                "服务器响应延迟",
                "元素渲染延迟",
            ]
            solutions = [
                "增加超时时间",
                "添加显式等待",
                "检查网络连接",
                "使用 wait_for_load_state()",
            ]

        elif error_type == 'navigation':
            causes = [
                "重定向问题",
                "URL 不正确",
                "需要登录认证",
                "跨域限制",
            ]
            solutions = [
                "检查最终 URL",
                "确保已登录",
                "处理弹窗或确认框",
                "检查跨域设置",
            ]

        else:
            causes = ["未知错误"]
            solutions = ["查看详细日志", "检查页面状态"]

        # 记录错误历史
        self._error_history.append({
            'timestamp': datetime.now().isoformat(),
            'error_type': error_type,
            'message': error_message,
        })

        return ErrorDiagnosis(
            error_type=error_type,
            likely_causes=causes,
            suggested_solutions=solutions,
            related_elements=related_elements,
        )

    def _classify_error(self, error_message: str) -> str:
        """分类错误"""
        error_lower = error_message.lower()

        if 'not found' in error_lower or 'no element' in error_lower:
            return 'selector_not_found'
        elif 'timeout' in error_lower or 'timed out' in error_lower:
            return 'timeout'
        elif 'navigation' in error_lower or 'goto' in error_lower:
            return 'navigation'
        elif 'click' in error_lower:
            return 'click_failed'
        elif 'fill' in error_lower or 'type' in error_lower:
            return 'input_failed'
        else:
            return 'unknown'

    async def _find_similar_elements(self, context: Optional[dict]) -> List[str]:
        """查找相似元素"""
        if not context or 'selector' not in context:
            return []

        similar = []
        original = context['selector']

        # 生成相似选择器
        if original.startswith('#'):
            similar.append(original.replace('#', '.'))
        elif original.startswith('.'):
            similar.append(f"[class*='{original[1:]}']")

        return similar

    async def generate_test_code(
        self,
        scenario: str,
        include_assertions: bool = True,
    ) -> CodeSuggestion:
        """根据场景生成测试代码"""
        # 分析当前页面
        structure = await self._analyzer.get_page_structure()

        # 解析场景
        actions = self._parse_scenario(scenario)

        # 生成代码
        code_lines = [
            "async def test_generated():",
            "    async with EasyBrowser() as browser:",
        ]

        # 添加导航
        code_lines.append(f"        await browser.goto('{structure['url']}')")

        # 添加操作
        for action in actions:
            action_code = await self._generate_action_code(action)
            if action_code:
                code_lines.append(f"        {action_code}")

        # 添加断言
        if include_assertions:
            code_lines.append("        assert browser.page.url is not None")

        code = "\n".join(code_lines)

        return CodeSuggestion(
            code=code,
            description=f"根据场景 '{scenario}' 生成的测试代码",
            language="python",
            confidence=0.7,
        )

    def _parse_scenario(self, scenario: str) -> List[Dict]:
        """解析场景描述"""
        actions = []

        if '登录' in scenario:
            actions.append({'type': 'login', 'description': '用户登录'})

        if '点击' in scenario:
            match = re.search(r'点击["\']?(\S+)["\']?', scenario)
            if match:
                actions.append({'type': 'click', 'target': match.group(1)})

        if '输入' in scenario or '填写' in scenario:
            actions.append({'type': 'input', 'description': '填写表单'})

        return actions

    async def _generate_action_code(self, action: Dict) -> Optional[str]:
        """生成动作代码"""
        action_type = action.get('type')

        if action_type == 'click':
            target = action.get('target', '按钮')
            return f"await browser.click('button:has-text(\"{target}\")')"

        elif action_type == 'input':
            return "await browser.type_text('input', '测试值')"

        elif action_type == 'login':
            return "# TODO: 实现登录逻辑"

        return None
