from collections.abc import Awaitable, Callable
from pathlib import Path

import aiohttp
import jinja2
from astrbot.api import logger

from ..services import RenderData


class BaseRenderer:
    def __init__(self, session: aiohttp.ClientSession | None = None) -> None:
        self.template_dir = Path(__file__).resolve().parent.parent / "templates"
        self.template_env = jinja2.Environment(
            loader=jinja2.FileSystemLoader(str(self.template_dir)), autoescape=True
        )
        self._session = session

    def _generate_html(
        self, template_path: str, render_data: RenderData, sub_dir: str = ""
    ) -> str:
        """Render a Jinja2 template and inject a <base> tag for static assets."""
        template = self.template_env.get_template(template_path)
        html = template.render(**render_data)
        base_path = self.template_dir / sub_dir if sub_dir else self.template_dir
        base_url = base_path.as_uri() + "/"
        if "<head>" in html:
            return html.replace("<head>", f'<head><base href="{base_url}">', 1)
        return f'<base href="{base_url}">{html}'

    async def _handle_rpc_response(
        self, response: aiohttp.ClientResponse
    ) -> str | None:
        if response.status != 200:
            logger.error(f"[-] RPC 渲染服务器返回错误状态码: {response.status}")
            return None
        try:
            result = await response.json()
        except aiohttp.ContentTypeError:
            logger.error("[-] RPC 响应内容不是有效的 JSON")
            return None
        except (ValueError, TypeError, RuntimeError) as e:
            logger.error(f"[-] 解析 RPC JSON 响应失败: {e}")
            return None

        if not isinstance(result, dict):
            logger.error(f"[-] RPC 响应格式错误: {type(result)}")
            return None
        if "error" in result:
            logger.error(f"[-] RPC 渲染返回业务错误: {result['error']}")
            return None

        res_obj = result.get("result")
        if isinstance(res_obj, dict) and "image" in res_obj:
            image = res_obj["image"]
            return image if isinstance(image, str) else None

        logger.error(f"[-] RPC 响应中未找到 result.image: {result}")
        return None

    async def _render_via_rpc(
        self,
        rpc_url: str,
        html_content: str,
        selector: str,
        timeout: int = 30000,
        wait_time: float = 0,
    ) -> str | None:
        """Send HTML to the remote RPC renderer and return a base64 image."""
        if not rpc_url:
            return None

        import asyncio

        payload = {
            "jsonrpc": "2.0",
            "method": "screenshot",
            "params": {
                "html": html_content,
                "selector": selector,
                "wait_time": wait_time,
                "timeout": timeout,
                "scale": 3,
            },
            "id": int(asyncio.get_event_loop().time() * 1000),
        }
        client_timeout = aiohttp.ClientTimeout(total=timeout / 1000.0)

        try:
            if self._session and not self._session.closed:
                async with self._session.post(
                    rpc_url, json=payload, timeout=client_timeout
                ) as response:
                    return await self._handle_rpc_response(response)
            else:
                async with (
                    aiohttp.ClientSession() as session,
                    session.post(rpc_url, json=payload, timeout=client_timeout) as response,
                ):
                    return await self._handle_rpc_response(response)

        except aiohttp.ClientConnectorError as e:
            logger.error(f"[-] RPC 渲染服务器连接失败: {e}")
        except TimeoutError:
            logger.error(f"[-] RPC 渲染请求超时 ({timeout}ms)")
        except aiohttp.ClientResponseError as e:
            logger.error(f"[-] RPC 渲染服务器响应异常: {e.status} {e.message}")
        except (RuntimeError, ValueError, TypeError) as e:
            logger.error(f"[-] RPC 渲染请求发生未知异常: {e}")
        return None

    async def render(
        self,
        template_path: str,
        render_data: RenderData,
        selector: str,
        local_render_func: Callable[[], Awaitable[str | None]],
        rpc_url: str | None = None,
        sub_dir: str = "",
        timeout: int = 30000,
        wait_time: float = 0,
    ) -> str | None:
        """
        Unified render entry point.

        Priority:
          1. Remote RPC server (if *rpc_url* is configured)
          2. Local Pillow rendering via *local_render_func*
        """
        if rpc_url:
            logger.debug(f"[+] 尝试通过 RPC 渲染: {template_path}")
            html_content = self._generate_html(template_path, render_data, sub_dir)
            result = await self._render_via_rpc(
                rpc_url=rpc_url,
                html_content=html_content,
                selector=selector,
                timeout=timeout,
                wait_time=wait_time,
            )
            if result:
                return result
            logger.warning(f"[-] RPC 渲染失败 ({template_path})，回退到本地 Pillow 渲染...")

        logger.debug(f"[+] 本地 Pillow 渲染: {template_path}")
        try:
            return await local_render_func()
        except (RuntimeError, ValueError, TypeError) as e:
            logger.error(f"[-] 本地 Pillow 渲染失败 ({template_path}): {e}")
            return None
