"""
iframe 管理器
iframe 深度穿透，自动切换上下文
"""
from dataclasses import dataclass
from typing import Optional, List, Callable, TypeVar, Any
from playwright.async_api import Page, Frame, Locator
from loguru import logger

T = TypeVar('T')


@dataclass
class IframeInfo:
    """iframe 信息"""
    name: str
    selector: str
    index: int
    depth: int
    src: Optional[str] = None


class IframeManager:
    """
    iframe 管理器
    处理 iframe 的查找、切换、操作
    """

    def __init__(self, page: Page):
        self.page = page
        self._frame_stack: List[Frame] = []
        self._current_frame: Optional[Frame] = None

    # ==================== iframe 查找 ====================

    async def get_iframe_info_list(self) -> List[IframeInfo]:
        """获取所有 iframe 信息"""
        frames = self.page.frames
        info_list: List[IframeInfo] = []

        for i, frame in enumerate(frames):
            if frame == self.page.main_frame:
                continue

            info = await self._get_frame_info(frame, i)
            info_list.append(info)

        return info_list

    async def _get_frame_info(self, frame: Frame, index: int) -> IframeInfo:
        """获取单个 iframe 信息"""
        name = frame.name or f"iframe-{index}"
        selector = await self._find_frame_selector(frame)
        depth = self._calculate_frame_depth(frame)
        src = await self._get_frame_src(frame)

        return IframeInfo(
            name=name,
            selector=selector,
            index=index,
            depth=depth,
            src=src,
        )

    async def _find_frame_selector(self, frame: Frame) -> str:
        """查找 iframe 选择器"""
        name = frame.name
        if name:
            return f'iframe[name="{name}"]'

        src = await self._get_frame_src(frame)
        if src:
            return f'iframe[src*="{src[:50]}"]'

        return "iframe"

    async def _get_frame_src(self, frame: Frame) -> Optional[str]:
        """获取 iframe 的 src"""
        try:
            frame_element = await frame.frame_element()
            src = await frame_element.get_attribute("src")
            return src
        except Exception:
            return None

    def _calculate_frame_depth(self, frame: Frame) -> int:
        """计算 iframe 嵌套深度"""
        depth = 0
        current = frame.parent_frame

        while current and current != self.page.main_frame:
            depth += 1
            current = current.parent_frame

        return depth

    # ==================== iframe 切换 ====================

    async def enter_iframe(self, selector: str) -> Optional[Frame]:
        """进入 iframe"""
        try:
            frame_element = await self.page.locator(selector).element_handle()
            if not frame_element:
                return None

            content_frame = await frame_element.content_frame()
            if not content_frame:
                return None

            self._frame_stack.append(self._current_frame or self.page.main_frame)
            self._current_frame = content_frame

            logger.debug(f"进入 iframe: {selector}")
            return content_frame
        except Exception as e:
            logger.debug(f"进入 iframe 失败: {selector}, error={e}")
            return None

    async def enter_iframe_by_name(self, name: str) -> Optional[Frame]:
        """通过名称进入 iframe"""
        return await self.enter_iframe(f'iframe[name="{name}"]')

    async def enter_iframe_by_index(self, index: int) -> Optional[Frame]:
        """通过索引进入 iframe"""
        return await self.enter_iframe(f"iframe >> nth={index}")

    def exit_iframe(self) -> Optional[Frame]:
        """退出当前 iframe"""
        if not self._frame_stack:
            self._current_frame = None
            return self.page.main_frame

        self._current_frame = self._frame_stack.pop()
        logger.debug(f"退出 iframe, 当前层级: {len(self._frame_stack)}")
        return self._current_frame

    def exit_to_main_frame(self):
        """退出到主框架"""
        self._frame_stack.clear()
        self._current_frame = None
        logger.debug("退出到主框架")

    def get_current_frame(self) -> Frame:
        """获取当前框架"""
        return self._current_frame or self.page.main_frame

    def get_stack_depth(self) -> int:
        """获取框架堆栈深度"""
        return len(self._frame_stack)

    # ==================== iframe 操作 ====================

    def locate(self, selector: str) -> Locator:
        """在当前框架中定位元素"""
        frame = self.get_current_frame()
        return frame.locator(selector)

    async def execute_in_frame(
        self,
        selector: str,
        action: Callable[[Frame], Any]
    ) -> Any:
        """在指定 iframe 中执行操作"""
        frame = await self.enter_iframe(selector)
        if not frame:
            return None

        try:
            if asyncio.iscoroutinefunction(action):
                return await action(frame)
            else:
                return action(frame)
        finally:
            self.exit_iframe()

    async def execute_in_nested_frame(
        self,
        selectors: List[str],
        action: Callable[[Frame], Any]
    ) -> Any:
        """在嵌套 iframe 中执行操作"""
        import asyncio

        entered_frames: List[str] = []

        try:
            # 依次进入嵌套的 iframe
            for selector in selectors:
                frame = await self.enter_iframe(selector)
                if not frame:
                    raise RuntimeError(f"无法进入 iframe: {selector}")
                entered_frames.append(selector)

            # 执行操作
            current_frame = self.get_current_frame()
            if asyncio.iscoroutinefunction(action):
                return await action(current_frame)
            else:
                return action(current_frame)
        except Exception as e:
            logger.error(f"嵌套 iframe 操作失败: {e}")
            return None
        finally:
            # 退出所有进入的 iframe
            for _ in entered_frames:
                self.exit_iframe()

    # ==================== iframe 等待 ====================

    async def wait_for_iframe(
        self,
        selector: str,
        timeout: float = 30000
    ) -> Optional[Frame]:
        """等待 iframe 加载"""
        try:
            frame_element = self.page.locator(selector)
            await frame_element.wait_for(state="attached", timeout=timeout)

            handle = await frame_element.element_handle()
            if not handle:
                return None

            frame = await handle.content_frame()
            if not frame:
                return None

            # 等待 iframe 内容加载
            try:
                await frame.wait_for_load_state("domcontentloaded", timeout=timeout)
            except Exception:
                pass

            return frame
        except Exception:
            return None

    async def wait_for_element_in_iframe(
        self,
        iframe_selector: str,
        element_selector: str,
        timeout: float = 30000
    ) -> Optional[Locator]:
        """等待 iframe 内元素出现"""
        try:
            frame = await self.wait_for_iframe(iframe_selector, timeout)
            if not frame:
                return None

            element = frame.locator(element_selector)
            await element.wait_for(state="visible", timeout=timeout)

            return element
        except Exception:
            return None

    # ==================== iframe 状态检测 ====================

    async def has_iframe(self, selector: str) -> bool:
        """检测 iframe 是否存在"""
        count = await self.page.locator(selector).count()
        return count > 0

    async def is_iframe_visible(self, selector: str) -> bool:
        """检测 iframe 是否可见"""
        try:
            return await self.page.locator(selector).is_visible()
        except Exception:
            return False

    async def get_iframe_count(self) -> int:
        """获取 iframe 数量"""
        return await self.page.locator("iframe").count()

    async def is_iframe_interactable(self, selector: str) -> bool:
        """检测 iframe 是否可交互"""
        try:
            frame = await self.wait_for_iframe(selector, 5000)
            if not frame:
                return False

            # 检查 iframe 是否有内容
            body = frame.locator("body")
            content = await body.inner_html()
            return len(content) > 0
        except Exception:
            return False

    # ==================== iframe 内容操作 ====================

    async def get_iframe_content(self, selector: str) -> Optional[str]:
        """获取 iframe 内容"""
        try:
            frame = await self.wait_for_iframe(selector)
            if not frame:
                return None

            return await frame.content()
        except Exception:
            return None

    async def click_in_iframe(
        self,
        iframe_selector: str,
        element_selector: str
    ) -> bool:
        """在 iframe 中点击元素"""
        try:
            element = await self.wait_for_element_in_iframe(
                iframe_selector, element_selector
            )
            if not element:
                return False

            await element.click()
            logger.debug(f"在 iframe 中点击: {element_selector}")
            return True
        except Exception:
            return False

    async def fill_in_iframe(
        self,
        iframe_selector: str,
        element_selector: str,
        value: str
    ) -> bool:
        """在 iframe 中填写表单"""
        try:
            element = await self.wait_for_element_in_iframe(
                iframe_selector, element_selector
            )
            if not element:
                return False

            await element.fill(value)
            logger.debug(f"在 iframe 中填写: {element_selector}")
            return True
        except Exception:
            return False


import asyncio


def create_iframe_manager(page: Page) -> IframeManager:
    """创建 iframe 管理器实例"""
    return IframeManager(page)
