from __future__ import annotations

import asyncio
import subprocess
import sys
from pathlib import Path

from playwright.async_api import async_playwright


ROOT = Path(__file__).resolve().parents[1]
ASSETS = ROOT / "assets"

HTML_FIGURES = [
    {
        "name": "governance_loop",
        "src": ROOT / "figures" / "html" / "governance_loop.html",
        "dst": ASSETS / "governance_loop.png",
        "width": 1600,
        "height": 1000,
    },
]

DOT_FIGURES = [
    {
        "name": "threat_model",
        "src": ROOT / "figures" / "dot" / "threat_model.dot",
        "dst": ASSETS / "threat_model.png",
        "engine": "dot",
    },
    {
        "name": "attack_chain",
        "src": ROOT / "figures" / "dot" / "attack_chain.dot",
        "dst": ASSETS / "attack_chain.png",
        "engine": "dot",
    },
    {
        "name": "overall_architecture",
        "src": ROOT / "figures" / "dot" / "overall_architecture.dot",
        "dst": ASSETS / "overall_architecture.png",
        "engine": "dot",
    },
    {
        "name": "training_round",
        "src": ROOT / "figures" / "dot" / "training_round.dot",
        "dst": ASSETS / "training_round.png",
        "engine": "dot",
    },
]


def render_dot_figure(figure: dict[str, object]) -> None:
    cmd = [
        str(figure["engine"]),
        "-Tpng:cairo",
        str(figure["src"]),
        "-o",
        str(figure["dst"]),
    ]
    subprocess.run(cmd, check=True)


async def render_html_figures() -> None:
    async with async_playwright() as p:
        browser = await p.chromium.launch(
            headless=True,
            args=[
                "--allow-file-access-from-files",
                "--font-render-hinting=medium",
                "--force-color-profile=srgb",
                "--disable-web-security",
            ],
        )
        try:
            for figure in HTML_FIGURES:
                page = await browser.new_page(
                    viewport={
                        "width": int(figure["width"]),
                        "height": int(figure["height"]),
                    },
                    device_scale_factor=2,
                )
                await page.goto(figure["src"].resolve().as_uri(), wait_until="networkidle")
                await page.wait_for_timeout(200)

                overflow = await page.evaluate(
                    """() => {
                        const root = document.documentElement;
                        const pageOverflow = {
                          width: root.scrollWidth > window.innerWidth + 2,
                          height: root.scrollHeight > window.innerHeight + 2,
                          scrollWidth: root.scrollWidth,
                          scrollHeight: root.scrollHeight,
                          viewportWidth: window.innerWidth,
                          viewportHeight: window.innerHeight,
                        };
                        const problems = [];
                        for (const el of document.querySelectorAll('[data-check]')) {
                          const dx = el.scrollWidth - el.clientWidth;
                          const dy = el.scrollHeight - el.clientHeight;
                          if (dx > 2 || dy > 2) {
                            problems.push({
                              key: el.getAttribute('data-check'),
                              dx,
                              dy,
                              clientWidth: el.clientWidth,
                              clientHeight: el.clientHeight,
                              scrollWidth: el.scrollWidth,
                              scrollHeight: el.scrollHeight
                            });
                          }
                        }
                        return { pageOverflow, problems };
                    }"""
                )

                if overflow["pageOverflow"]["width"] or overflow["pageOverflow"]["height"]:
                    raise RuntimeError(
                        f"{figure['name']} page overflow: {overflow['pageOverflow']}"
                    )
                if overflow["problems"]:
                    raise RuntimeError(
                        f"{figure['name']} element overflow: {overflow['problems']}"
                    )

                await page.screenshot(
                    path=str(figure["dst"]),
                    clip={
                        "x": 0,
                        "y": 0,
                        "width": int(figure["width"]),
                        "height": int(figure["height"]),
                    },
                )
                await page.close()
        finally:
            await browser.close()


async def main() -> int:
    ASSETS.mkdir(exist_ok=True)

    for figure in DOT_FIGURES:
        render_dot_figure(figure)

    await render_html_figures()
    return 0


if __name__ == "__main__":
    try:
        raise SystemExit(asyncio.get_event_loop().run_until_complete(main()))
    except subprocess.CalledProcessError as exc:
        print(f"Graphviz rendering failed: {exc}", file=sys.stderr)
        raise SystemExit(1)
    except Exception as exc:  # noqa: BLE001
        print(f"Figure rendering failed: {exc}", file=sys.stderr)
        raise SystemExit(1)
