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

import cn.yunzhixue.ability.center.examsprint.domain.report.ExamSprintReportPdfGenerator;
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
import com.openhtmltopdf.svgsupport.BatikSVGDrawer;
import org.springframework.stereotype.Component;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;

@Component
public class OpenHtmlToPdfExamSprintReportPdfGenerator implements ExamSprintReportPdfGenerator {

    private static final List<String> FONT_CANDIDATES = List.of(
            "/System/Library/Fonts/Supplemental/Arial Unicode.ttf",
            "/Library/Fonts/Arial Unicode.ttf",
            "/usr/share/fonts/truetype/noto/NotoSansSC-Regular.ttf",
            "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttf",
            "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
            "/System/Library/Fonts/Hiragino Sans GB.ttc",
            "/System/Library/Fonts/Supplemental/Songti.ttc");

    private final Supplier<BundledOutlookReportFonts> bundledFontsSupplier;
    private final List<String> fontCandidates;

    public OpenHtmlToPdfExamSprintReportPdfGenerator() {
        this(BundledOutlookReportFonts::load, FONT_CANDIDATES);
    }

    OpenHtmlToPdfExamSprintReportPdfGenerator(
            Supplier<BundledOutlookReportFonts> bundledFontsSupplier,
            List<String> fontCandidates) {
        this.bundledFontsSupplier = Objects.requireNonNull(bundledFontsSupplier, "bundledFontsSupplier");
        this.fontCandidates = List.copyOf(Objects.requireNonNull(fontCandidates, "fontCandidates"));
    }

    @Override
    public byte[] generate(String htmlContent) {
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            PdfRendererBuilder builder = new PdfRendererBuilder();
            builder.useFastMode();
            builder.withHtmlContent(htmlContent, null);
            builder.useSVGDrawer(new BatikSVGDrawer());
            registerAvailableFont(builder);
            builder.toStream(outputStream);
            builder.run();
            return outputStream.toByteArray();
        } catch (IOException exception) {
            throw new UncheckedIOException("Failed to generate PDF", exception);
        } catch (Exception exception) {
            throw new IllegalStateException("Failed to generate PDF", exception);
        }
    }

    private void registerAvailableFont(PdfRendererBuilder builder) {
        if (registerBundledFonts(builder)) {
            return;
        }

        registerSystemFont(builder);
    }

    private boolean registerBundledFonts(PdfRendererBuilder builder) {
        BundledOutlookReportFonts bundledFonts;
        try {
            bundledFonts = Objects.requireNonNull(bundledFontsSupplier.get(), "bundledFonts");
        } catch (BundledOutlookReportFonts.BundledFontMissingException exception) {
            return false;
        }

        boolean registered = false;
        for (BundledOutlookReportFonts.Registration registration : bundledFonts.registrations()) {
            File fontFile = registration.file();
            if (!isReadableFile(fontFile)) {
                throw new IllegalStateException("Bundled font file is not readable: " + fontFile);
            }
            builder.useFont(fontFile, registration.family());
            builder.useFont(fontFile, "ReportFont");
            registered = true;
        }
        return registered;
    }

    private void registerSystemFont(PdfRendererBuilder builder) {
        for (String candidate : fontCandidates) {
            File fontFile = new File(candidate);
            if (fontFile.exists()) {
                builder.useFont(fontFile, "ReportFont");
                return;
            }
        }
    }

    private boolean isReadableFile(File file) {
        return file.exists() && file.isFile() && file.canRead();
    }
}
