#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：smart_locate_service.py
模块描述：智能定位服务，自动访问被测系统、执行操作、提取选择器

作者：czj
创建日期：2026-08-05
最后修改：2026-08-05
"""

import logging
import os
import base64
from typing import List, Dict, Any, Optional
from datetime import datetime

from app.executors.playwright_executor import PlaywrightExecutor
from app.services.keyword_matcher import (
    extract_keywords,
    extract_value_from_description,
    detect_action_type,
    match_element_by_keywords,
    find_element_by_semantic
)
from app.services.selector_extractor import (
    extract_selectors,
    extract_element_info,
    build_step_with_selectors
)
from app.services.login_template_service import LoginTemplateService
from app.config import settings

logger = logging.getLogger(__name__)


class SmartLocateService:
    """
    智能定位服务

    核心流程：
    1. 启动 Playwright 浏览器
    2. 执行登录模板（自动登录）
    3. 导航到目标菜单
    4. 逐步骤定位 + 执行
    5. 返回所有步骤的选择器

    特点：
    - 不依赖 Claude CLI，通过实际执行验证选择器
    - 支持登录模板自动执行
    - 支持功能中心导航
    - 提取多候选选择器，按优先级回退
    """

    def __init__(self):
        """初始化智能定位服务"""
        self.executor: Optional[PlaywrightExecutor] = None
        self.screenshot_dir = settings.SCREENSHOT_DIR

    def locate_steps(
        self,
        steps: List[Dict[str, Any]],
        auto_login: bool = True,
        navigate_menu: str = "",
        page_url: str = "https://192.168.5.44"
    ) -> List[Dict[str, Any]]:
        """
        智能定位主流程

        Args:
            steps (List[Dict]): 步骤列表，格式：
                [
                    {"order": 1, "name": "输入用户名", "action": "fill", "params": {"value": "admin@xty"}},
                    {"order": 2, "name": "点击登录按钮", "action": "click", "params": {}}
                ]
            auto_login (bool): 是否自动登录
            navigate_menu (str): 目标菜单名称（如"信息发布"）
            page_url (str): 被测系统基础 URL

        Returns:
            List[Dict]: 定位结果列表，格式：
                [
                    {
                        "order": 1,
                        "name": "输入用户名",
                        "success": True,
                        "action": "fill",
                        "params": {"selector": "...", "value": "admin@xty"},
                        "selectors": {...},
                        "screenshot": "base64..."
                    }
                ]
        """
        logger.info(f"开始智能定位: {len(steps)} 个步骤, auto_login={auto_login}, navigate_menu={navigate_menu}")

        results = []

        try:
            # 1. 启动 Playwright
            logger.info("启动 Playwright 浏览器...")
            self.executor = PlaywrightExecutor({
                'headless': settings.PLAYWRIGHT_HEADLESS,
                'timeout': 30000,
                'screenshot': True,
                'screenshot_dir': self.screenshot_dir,
                'ignore_https_errors': True,
            })
            self.executor.start()

            # 2. 自动登录
            if auto_login:
                logger.info("执行自动登录...")
                login_success = self._do_login_template()
                if not login_success:
                    logger.warning("自动登录失败，可能影响后续步骤定位")
                else:
                    logger.info("自动登录成功")

            # 3. 导航到目标菜单
            if navigate_menu:
                logger.info(f"导航到目标菜单: {navigate_menu}")
                nav_success = self._navigate_to_menu(navigate_menu)
                if not nav_success:
                    logger.warning(f"导航到菜单 '{navigate_menu}' 失败")

            # 4. 逐步骤定位
            for step in steps:
                result = self._locate_single_step(step)
                results.append(result)

                # 如果定位成功且操作类型需要执行，则实际执行（确保页面状态正确）
                if result.get('success') and result.get('action') != 'navigate':
                    self._execute_step(result)

            logger.info(f"智能定位完成: {len(results)} 个步骤")

        except Exception as e:
            logger.error(f"智能定位异常: {e}")
            # 将异常信息添加到结果
            if not results:
                results.append({
                    'order': 0,
                    'name': '智能定位',
                    'success': False,
                    'message': f'定位异常: {str(e)}'
                })

        finally:
            # 5. 关闭浏览器
            if self.executor:
                try:
                    self.executor.stop()
                    logger.info("Playwright 浏览器已关闭")
                except Exception as e:
                    logger.warning(f"关闭浏览器失败: {e}")

        return results

    def _do_login_template(self) -> bool:
        """
        执行登录模板（使用 PlaywrightExecutor 内置的 do_login 方法）

        Returns:
            bool: 是否登录成功
        """
        try:
            return self.executor.do_login()
        except Exception as e:
            logger.error(f"执行登录模板失败: {e}")
            return False

    def _navigate_to_menu(self, menu_name: str) -> bool:
        """
        导航到目标菜单（功能中心 → 抽屉 → 目标菜单）

        Args:
            menu_name (str): 目标菜单名称

        Returns:
            bool: 是否导航成功
        """
        try:
            page = self.executor._page

            # 步骤 1: 点击功能中心图标
            logger.debug("点击功能中心图标...")
            try:
                # XPath 选择器（已验证）
                page.click('//*[@id="Home"]/div[1]/div[1]', timeout=5000)
                page.wait_for_timeout(1000)
            except Exception as e:
                logger.warning(f"点击功能中心图标失败: {e}")
                # 回退：尝试 class 选择器
                try:
                    page.click('.home_nav_left', timeout=5000)
                except Exception:
                    logger.error("无法点击功能中心图标")
                    return False

            # 步骤 2: 等待功能抽屉打开
            logger.debug("等待功能抽屉打开...")
            try:
                page.wait_for_selector('.el-drawer', timeout=5000)
            except Exception as e:
                logger.warning(f"等待抽屉超时: {e}")

            # 步骤 3: 点击目标菜单
            logger.debug(f"点击目标菜单: {menu_name}")
            try:
                # 组合选择器：.el-drawer 内包含菜单文本的元素
                menu_selector = f'.el-drawer >> text="{menu_name}"'
                page.click(menu_selector, timeout=5000)
                page.wait_for_timeout(2000)
                logger.info(f"成功导航到菜单: {menu_name}")
                return True
            except Exception as e:
                logger.error(f"点击菜单失败: {e}")
                return False

        except Exception as e:
            logger.error(f"导航到菜单异常: {e}")
            return False

    def _locate_single_step(self, step: Dict[str, Any]) -> Dict[str, Any]:
        """
        为单个步骤定位元素

        Args:
            step (Dict): 步骤定义

        Returns:
            Dict: 定位结果
        """
        order = step.get('order', 0)
        name = step.get('name', '')
        action = step.get('action', '')
        params = step.get('params', {})

        logger.info(f"定位步骤 {order}: {name} (action={action})")

        result = {
            'order': order,
            'name': name,
            'success': False,
            'action': action,
            'params': params,
            'selectors': {'primary': None, 'candidates': []},
            'element_info': {},
            'screenshot': None,
            'message': ''
        }

        try:
            # navigate 类型不需要定位
            if action == 'navigate':
                result['success'] = True
                result['message'] = '导航步骤无需定位'
                return result

            # 提取关键词
            keywords = extract_keywords(name)
            logger.debug(f"提取关键词: {keywords}")

            page = self.executor._page

            # 第一级：关键词直接匹配
            element, selectors = match_element_by_keywords(page, keywords, action)

            if element and selectors:
                result['success'] = True
                # 将 selectors 列表转换为字典格式
                primary_selector = selectors[0]['value'] if selectors else None
                result['selectors'] = {
                    'primary': primary_selector,
                    'candidates': selectors
                }
                result['element_info'] = extract_element_info(element)
                result['message'] = f'关键词匹配成功: {primary_selector}'
                logger.info(f"步骤 {order} 定位成功（关键词匹配）: {primary_selector}")
            else:
                # 第二级：语义推断
                element, selectors = find_element_by_semantic(page, keywords, action)

                if element and selectors:
                    result['success'] = True
                    # 将 selectors 列表转换为字典格式
                    primary_selector = selectors[0]['value'] if selectors else None
                    result['selectors'] = {
                        'primary': primary_selector,
                        'candidates': selectors
                    }
                    result['element_info'] = extract_element_info(element)
                    result['message'] = f'语义推断成功: {primary_selector}'
                    logger.info(f"步骤 {order} 定位成功（语义推断）: {primary_selector}")
                else:
                    result['success'] = False
                    result['message'] = f'未找到匹配元素: {name}'
                    logger.warning(f"步骤 {order} 定位失败: {result['message']}")

            # 更新 params 中的 selector
            if result['success'] and result['selectors'].get('primary'):
                result['params']['selector'] = result['selectors']['primary']

            # 截图
            try:
                screenshot_path = os.path.join(
                    self.screenshot_dir,
                    f"smart_locate_{order}_{datetime.now().strftime('%H%M%S')}.png"
                )
                page.screenshot(path=screenshot_path)
                with open(screenshot_path, 'rb') as f:
                    result['screenshot'] = base64.b64encode(f.read()).decode('utf-8')
            except Exception as e:
                logger.debug(f"截图失败（非致命）: {e}")

        except Exception as e:
            result['success'] = False
            result['message'] = f'定位异常: {str(e)}'
            logger.error(f"步骤 {order} 定位异常: {e}")

        return result

    def _execute_step(self, result: Dict[str, Any]) -> bool:
        """
        实际执行步骤（验证选择器是否有效）

        Args:
            result (Dict): 定位结果（包含 selector 和 params）

        Returns:
            bool: 是否执行成功
        """
        action = result.get('action', '')
        params = result.get('params', {})
        selector = params.get('selector', '')

        if not selector:
            return False

        logger.debug(f"执行步骤验证: action={action}, selector={selector}")

        try:
            page = self.executor._page

            if action == 'fill':
                value = params.get('value', '')
                # 等待元素可见
                page.wait_for_selector(selector, timeout=5000)
                # 填充
                page.fill(selector, str(value))
                logger.debug(f"填充成功: {selector} = {value}")

            elif action == 'click':
                # 等待元素可见
                page.wait_for_selector(selector, timeout=5000)
                # 点击
                page.click(selector)
                logger.debug(f"点击成功: {selector}")
                # 等待可能的页面变化
                page.wait_for_timeout(1000)

            elif action == 'wait':
                timeout = params.get('timeout', 10000)
                page.wait_for_selector(selector, timeout=timeout)
                logger.debug(f"等待成功: {selector}")

            elif action == 'select':
                value = params.get('value', '')
                page.wait_for_selector(selector, timeout=5000)
                page.select_option(selector, str(value))
                logger.debug(f"选择成功: {selector} = {value}")

            return True

        except Exception as e:
            logger.warning(f"执行步骤验证失败: {e}")
            return False


# ==================== 工厂函数 ====================

def get_smart_locate_service() -> SmartLocateService:
    """
    获取智能定位服务实例

    Returns:
        SmartLocateService: 服务实例
    """
    return SmartLocateService()