"""
混合引擎
结合 LLM 和规则引擎，提供智能回退机制
"""
import json
from typing import Optional, List

from playwright.async_api import Page
from loguru import logger

from .base import BaseEngine
from .rule_engine import RuleEngine
from ..llm.factory import create_llm_provider
from ..llm.base import BaseLLMProvider, LLMResponse
from ..types import (
    AIConfig,
    SelectorSuggestion,
    ErrorDiagnosis,
    CodeSuggestion,
    LLMProvider as ProviderEnum,
)


class HybridEngine(BaseEngine):
    """
    混合引擎：LLM + 规则引擎

    工作流程:
    1. 如果配置为 RULE 提供商，直接使用规则引擎
    2. 如果 use_llm_first=True，优先尝试 LLM
    3. 如果 LLM 失败且 fallback_to_rule=True，回退到规则引擎
    """

    def __init__(self, page: Page, config: Optional[AIConfig] = None):
        self.page = page
        self.config = config or AIConfig()
        self._rule_engine = RuleEngine(page, self.config)
        self._llm: Optional[BaseLLMProvider] = None

    @property
    def llm(self) -> Optional[BaseLLMProvider]:
        """获取 LLM 提供商（延迟初始化）"""
        if self._llm is None and self.config.llm.provider != ProviderEnum.RULE:
            self._llm = create_llm_provider(self.config.llm)
        return self._llm

    async def suggest_selectors(
        self,
        description: str,
        element_type: Optional[str] = None,
    ) -> List[SelectorSuggestion]:
        """
        智能选择器建议

        优先使用 LLM，失败时回退到规则引擎
        """
        # 尝试 LLM
        if self.config.use_llm_first and self.llm:
            suggestions = await self._llm_suggest_selectors(description, element_type)
            if suggestions:
                logger.debug(f"LLM 生成了 {len(suggestions)} 个选择器建议")
                return suggestions

        # 回退到规则引擎
        if self.config.fallback_to_rule or not self.llm:
            return await self._rule_engine.suggest_selectors(description, element_type)

        return []

    async def _llm_suggest_selectors(
        self,
        description: str,
        element_type: Optional[str],
    ) -> Optional[List[SelectorSuggestion]]:
        """使用 LLM 生成选择器建议"""
        prompt = f"""分析以下元素描述，生成最合适的 CSS 选择器。

描述: {description}
元素类型: {element_type or "未知"}

请以 JSON 格式返回选择器建议列表:
[
  {{"selector": "...", "confidence": 0.9, "strategy": "..."}}
]

要求:
1. 只返回 JSON 数组，不要其他内容
2. confidence 是 0-1 之间的浮点数
3. strategy 描述选择器策略（如 "id", "class", "text" 等）
4. 最多返回 5 个建议"""

        response = await self.llm.call(prompt, system_prompt=self._get_system_prompt())

        if not response.success:
            logger.warning(f"LLM 调用失败: {response.error}")
            return None

        # 解析 JSON 响应
        try:
            # 尝试提取 JSON 数组
            content = response.content.strip()
            # 处理可能的 markdown 代码块
            if content.startswith("```"):
                lines = content.split("\n")
                content = "\n".join(lines[1:-1] if lines[-1] == "```" else lines[1:])

            data = json.loads(content)
            if not isinstance(data, list):
                data = [data]

            suggestions = []
            for item in data[:self.config.max_suggestions]:
                selector = item.get("selector", "")
                if not selector:
                    continue

                # 验证选择器
                confidence = await self._validate_selector(selector)
                if confidence > 0:
                    suggestions.append(SelectorSuggestion(
                        selector=selector,
                        confidence=min(confidence, item.get("confidence", 0.8)),
                        strategy=item.get("strategy", "llm"),
                        element_type=element_type or "unknown",
                        description="由 LLM 生成",
                    ))

            return suggestions if suggestions else None

        except json.JSONDecodeError as e:
            logger.warning(f"LLM 返回的 JSON 解析失败: {e}")
            return None
        except Exception as e:
            logger.warning(f"处理 LLM 响应时出错: {e}")
            return None

    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:
        """诊断错误"""
        # 尝试 LLM 诊断
        if self.config.use_llm_first and self.llm:
            diagnosis = await self._llm_diagnose_error(error, context)
            if diagnosis:
                logger.debug("LLM 完成了错误诊断")
                return diagnosis

        # 回退到规则引擎
        return await self._rule_engine.diagnose_error(error, context)

    async def _llm_diagnose_error(
        self,
        error: Exception,
        context: Optional[dict],
    ) -> Optional[ErrorDiagnosis]:
        """使用 LLM 诊断错误"""
        error_message = str(error)
        context_str = f"\n上下文: {context}" if context else ""

        prompt = f"""分析以下自动化测试错误，诊断原因并提供解决方案。

错误信息: {error_message}{context_str}

请以 JSON 格式返回诊断结果:
{{
  "error_type": "错误类型",
  "likely_causes": ["原因1", "原因2"],
  "suggested_solutions": ["解决方案1", "解决方案2"],
  "related_elements": ["相关元素选择器"]
}}

只返回 JSON，不要其他内容。"""

        response = await self.llm.call(prompt, system_prompt=self._get_system_prompt())

        if not response.success:
            return None

        try:
            content = response.content.strip()
            if content.startswith("```"):
                lines = content.split("\n")
                content = "\n".join(lines[1:-1] if lines[-1] == "```" else lines[1:])

            data = json.loads(content)

            return ErrorDiagnosis(
                error_type=data.get("error_type", "unknown"),
                likely_causes=data.get("likely_causes", []),
                suggested_solutions=data.get("suggested_solutions", []),
                related_elements=data.get("related_elements", []),
            )
        except Exception as e:
            logger.warning(f"解析 LLM 错误诊断结果失败: {e}")
            return None

    async def generate_test_code(
        self,
        scenario: str,
        include_assertions: bool = True,
    ) -> CodeSuggestion:
        """生成测试代码"""
        # 尝试 LLM 生成
        if self.config.use_llm_first and self.llm:
            code = await self._llm_generate_test_code(scenario, include_assertions)
            if code:
                logger.debug("LLM 生成了测试代码")
                return code

        # 回退到规则引擎
        return await self._rule_engine.generate_test_code(scenario, include_assertions)

    async def _llm_generate_test_code(
        self,
        scenario: str,
        include_assertions: bool,
    ) -> Optional[CodeSuggestion]:
        """使用 LLM 生成测试代码"""
        assertions_str = "包含断言" if include_assertions else "不包含断言"

        prompt = f"""根据以下场景生成 Python Playwright 自动化测试代码。

场景: {scenario}
要求: {assertions_str}

使用 playwright-ez 库的 EasyBrowser 类:
```python
async with EasyBrowser() as browser:
    await browser.goto("url")
    await browser.click("selector")
```

只返回代码，不要解释。"""

        response = await self.llm.call(prompt, system_prompt=self._get_system_prompt())

        if not response.success:
            return None

        try:
            code = response.content.strip()
            # 移除可能的 markdown 代码块标记
            if code.startswith("```python"):
                lines = code.split("\n")
                code = "\n".join(lines[1:-1] if lines[-1] == "```" else lines[1:])
            elif code.startswith("```"):
                lines = code.split("\n")
                code = "\n".join(lines[1:-1] if lines[-1] == "```" else lines[1:])

            return CodeSuggestion(
                code=code,
                description=f"根据场景 '{scenario}' 生成的测试代码",
                language="python",
                confidence=0.8,
            )
        except Exception as e:
            logger.warning(f"处理 LLM 生成的代码失败: {e}")
            return None

    def _get_system_prompt(self) -> str:
        """获取系统提示"""
        return """你是一个 Web 自动化测试专家，精通 Playwright 和 CSS 选择器。
你的任务是帮助用户：
1. 分析网页元素，生成精确的 CSS 选择器
2. 诊断自动化测试中的错误，提供解决方案
3. 根据测试场景生成自动化测试代码

请始终返回有效的 JSON 格式数据。"""
