# This file is part of https://github.com/KurtBoehm/svg_path_editor.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

import subprocess
import sys
from contextlib import redirect_stdout
from io import StringIO
from pathlib import Path
from typing import Callable

import fitz
import numpy as np
import pytest
import qrcode

from polyqr import QrCodePainter, run_tikz

from .defs import test_messages

# Minimal LaTeX wrapper used to compile the TikZ snippet into a PDF.
_LATEX_TEMPLATE = """
\\documentclass[tikz]{{standalone}}
\\usepackage{{tikz}}
\\begin{{document}}
{body}
\\end{{document}}
"""


def _test_tikz(msg: str, make_tikz: Callable[[], str], tmp_path: Path) -> None:
    """
    Check that the TikZ code generated by ``make_tikz``, when rendered using
    pdfLaTeX and rasterized PyMuPDF, is equivalent to the output of
    :class:`qrcode.QRCode`.

    This function requires a working LaTeX installation with pdflatex and TikZ.
    """

    # Reference matrix (True = black)
    qr = qrcode.QRCode()
    qr.add_data(msg)
    qr.make()
    ref_matrix = np.array(qr.modules, dtype=bool)

    # Produce TikZ for the same message with an arbitrary module size.
    tikz = make_tikz()

    # Write the full LaTeX document and compile it to a single-page PDF.
    tex_path = tmp_path / "qr_test.tex"
    tex_path.write_text(_LATEX_TEMPLATE.format(body=tikz))

    # Run pdflatex in nonstop mode, stop on errors, and write outputs next to the .tex.
    subprocess.run(
        [
            "pdflatex",
            "-interaction=nonstopmode",
            "-halt-on-error",
            "-output-directory",
            tex_path.parent,
            tex_path,
        ],
        check=True,
    )
    pdf_path = tex_path.with_suffix(".pdf")

    # Render the PDF page to a grayscale image with a scale chosen so that there is
    # one pixel per module. Since the output is black-and-white, a simple mid-gray
    # threshold (128) produces a clean Boolean array.
    with fitz.Document(pdf_path) as doc:
        page = doc[0]
        # points → pixels at 1 pixel per module
        scale = qr.modules_count / page.rect.width
        pix = page.get_pixmap(
            matrix=fitz.Matrix(scale, scale),
            colorspace=fitz.csGRAY,
            alpha=False,
        )
        img = np.frombuffer(pix.samples, dtype=np.uint8).reshape(pix.height, pix.width)
        # ``True`` corresponds to black
        raster = img < 128

    assert np.array_equal(raster, ref_matrix), (
        f"Rendered QR code differs from reference for message: {msg!r}\n"
        f"Reference matrix (True=black):\n{ref_matrix}\n"
        f"Rendered matrix (True=black):\n{raster}"
    )


@pytest.mark.parametrize("msg", test_messages)
def test_qrcode_painter_tikz(msg: str, tmp_path: Path) -> None:
    """
    Test that the code produced by :meth:`QrCodePainter.tikz`, when rendered using
    pdfLaTeX and rasterized PyMuPDF, is equivalent to the output of
    :class:`qrcode.QRCode`.

    This test requires a working LaTeX installation with pdflatex and TikZ.
    """

    # Produce TikZ for the same message with an arbitrary module size.
    def make_tikz():
        painter = QrCodePainter(msg)
        return painter.tikz(size="1pt", style="")

    # Use the helper function
    _test_tikz(msg, make_tikz, tmp_path)


@pytest.mark.parametrize("msg", test_messages)
def test_run_tikz(msg: str, tmp_path: Path) -> None:
    """
    Test that the code printed by :function:`run_tikz`, when rendered using
    pdfLaTeX and rasterized PyMuPDF, is equivalent to the output of
    :class:`qrcode.QRCode`.

    This test requires a working LaTeX installation with pdflatex and TikZ.
    """

    # Produce TikZ for the same message with an arbitrary module size.
    def make_tikz():
        sys.argv = [sys.argv[0], "1pt", "", msg]
        f = StringIO()
        with redirect_stdout(f):
            run_tikz()
        return f.getvalue()

    # Use the helper function
    _test_tikz(msg, make_tikz, tmp_path)
