package cn.yunzhixue.ability.center.examsprint.infrastructure.report.pdf;

import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.options.Margin;
import com.microsoft.playwright.options.Media;
import com.microsoft.playwright.options.WaitUntilState;

import java.util.Objects;

final class DefaultPlaywrightPdfWorker implements PlaywrightPdfWorker {

    private final Playwright playwright;
    private final Browser browser;
    private final double renderTimeoutMillis;
    private boolean closed;

    DefaultPlaywrightPdfWorker(double launchTimeoutMillis, double renderTimeoutMillis) {
        this.renderTimeoutMillis = renderTimeoutMillis;

        Playwright initializedPlaywright = null;
        Browser initializedBrowser = null;
        try {
            initializedPlaywright = Playwright.create();
            initializedBrowser = initializedPlaywright.chromium().launch(createLaunchOptions(launchTimeoutMillis));
        } catch (RuntimeException exception) {
            closeQuietly(initializedBrowser);
            closeQuietly(initializedPlaywright);
            throw new IllegalStateException("Failed to initialize Playwright PDF generator", exception);
        }

        this.playwright = initializedPlaywright;
        this.browser = initializedBrowser;
    }

    @Override
    public synchronized byte[] generate(String htmlContent) {
        Objects.requireNonNull(htmlContent, "htmlContent");
        ensureOpen();

        BrowserContext context = null;
        try {
            context = browser.newContext(new Browser.NewContextOptions().setLocale("zh-CN"));
            context.setDefaultTimeout(renderTimeoutMillis);
            context.setDefaultNavigationTimeout(renderTimeoutMillis);
            Page page = context.newPage();
            page.emulateMedia(new Page.EmulateMediaOptions().setMedia(Media.PRINT));
            page.setContent(htmlContent, new Page.SetContentOptions()
                    .setWaitUntil(WaitUntilState.LOAD)
                    .setTimeout(renderTimeoutMillis));
            page.evaluate("() => document.fonts ? document.fonts.ready.then(() => true) : true");
            return page.pdf(new Page.PdfOptions()
                    .setFormat("A4")
                    .setPrintBackground(true)
                    .setPreferCSSPageSize(true)
                    .setMargin(new Margin()
                            .setTop("0")
                            .setRight("0")
                            .setBottom("0")
                            .setLeft("0")));
        } catch (Exception exception) {
            throw new IllegalStateException("Failed to generate PDF", exception);
        } finally {
            closeQuietly(context);
        }
    }

    @Override
    public synchronized void close() {
        if (closed) {
            return;
        }

        RuntimeException closeFailure = null;
        try {
            browser.close();
        } catch (RuntimeException exception) {
            closeFailure = exception;
        }
        try {
            playwright.close();
        } catch (RuntimeException exception) {
            if (closeFailure == null) {
                closeFailure = exception;
            } else {
                closeFailure.addSuppressed(exception);
            }
        }
        closed = true;
        if (closeFailure != null) {
            throw closeFailure;
        }
    }

    static BrowserType.LaunchOptions createLaunchOptions(double launchTimeoutMillis) {
        return new BrowserType.LaunchOptions()
                .setHeadless(true)
                .setTimeout(launchTimeoutMillis);
    }

    private void ensureOpen() {
        if (closed) {
            throw new IllegalStateException("PDF worker is closed");
        }
    }

    private static void closeQuietly(AutoCloseable closeable) {
        if (closeable == null) {
            return;
        }
        try {
            closeable.close();
        } catch (Exception ignored) {
        }
    }
}
