"""Tests for tinypages build using sphinx extensions."""

import filecmp
import os
from pathlib import Path
import shutil
import sys

from matplotlib.testing import subprocess_run_for_testing
import pytest


pytest.importorskip('sphinx', minversion='4.1.3')


tinypages = Path(__file__).parent / 'data/tinypages'


def build_sphinx_html(source_dir, doctree_dir, html_dir, extra_args=None):
    # Build the pages with warnings turned into errors
    extra_args = [] if extra_args is None else extra_args
    cmd = [sys.executable, '-msphinx', '-W', '-b', 'html',
           '-d', str(doctree_dir), str(source_dir), str(html_dir), *extra_args]
    # On CI, gcov emits warnings (due to agg headers being included with the
    # same name in multiple extension modules -- but we don't care about their
    # coverage anyways); hide them using GCOV_ERROR_FILE.
    proc = subprocess_run_for_testing(
        cmd, capture_output=True, text=True,
        env={**os.environ, "MPLBACKEND": "", "GCOV_ERROR_FILE": os.devnull}
    )
    out = proc.stdout
    err = proc.stderr

    assert proc.returncode == 0, \
        f"sphinx build failed with stdout:\n{out}\nstderr:\n{err}\n"
    if err:
        pytest.fail(f"sphinx build emitted the following warnings:\n{err}")

    assert html_dir.is_dir()


def test_tinypages(tmp_path):
    shutil.copytree(tinypages, tmp_path, dirs_exist_ok=True,
                    ignore=shutil.ignore_patterns('_build', 'doctrees',
                                                   'plot_directive'))
    html_dir = tmp_path / '_build' / 'html'
    img_dir = html_dir / '_images'
    doctree_dir = tmp_path / 'doctrees'

    # Build the pages with warnings turned into errors
    build_sphinx_html(tmp_path, doctree_dir, html_dir)

    def plot_file(num):
        return img_dir / f'some_plots-{num}.png'

    def plot_directive_file(num):
        # This is always next to the doctree dir.
        return doctree_dir.parent / 'plot_directive' / f'some_plots-{num}.png'

    range_10, range_6, range_4 = (plot_file(i) for i in range(1, 4))
    # Plot 5 is range(6) plot
    assert filecmp.cmp(range_6, plot_file(5))
    # Plot 7 is range(4) plot
    assert filecmp.cmp(range_4, plot_file(7))
    # Plot 11 is range(10) plot
    assert filecmp.cmp(range_10, plot_file(11))
    # Plot 12 uses the old range(10) figure and the new range(6) figure
    assert filecmp.cmp(range_10, plot_file('12_00'))
    assert filecmp.cmp(range_6, plot_file('12_01'))
    # Plot 13 shows close-figs in action
    assert filecmp.cmp(range_4, plot_file(13))
    # Plot 14 has included source
    html_contents = (html_dir / 'some_plots.html').read_text(encoding='utf-8')

    assert '# Only a comment' in html_contents
    # check plot defined in external file.
    assert filecmp.cmp(range_4, img_dir / 'range4.png')
    assert filecmp.cmp(range_6, img_dir / 'range6_range6.png')
    # check if figure caption made it into html file
    assert 'This is the caption for plot 15.' in html_contents
    # check if figure caption using :caption: made it into html file (because this plot
    # doesn't use srcset, the caption preserves newlines in the output.)
    assert 'Plot 17 uses the caption option,\nwith multi-line input.' in html_contents
    # check if figure alt text using :alt: made it into html file
    assert 'Plot 17 uses the alt option, with multi-line input.' in html_contents
    # check if figure caption made it into html file
    assert 'This is the caption for plot 18.' in html_contents
    # check if the custom classes made it into the html file
    assert 'plot-directive my-class my-other-class' in html_contents
    # check that the multi-image caption is applied twice
    assert html_contents.count('This caption applies to both plots.') == 2
    # Plot 21 is range(6) plot via an include directive. But because some of
    # the previous plots are repeated, the argument to plot_file() is only 17.
    assert filecmp.cmp(range_6, plot_file(17))
    # plot 22 is from the range6.py file again, but a different function
    assert filecmp.cmp(range_10, img_dir / 'range6_range10.png')
    # plots 23--25 use a custom basename
    assert filecmp.cmp(range_6, img_dir / 'custom-basename-6.png')
    assert filecmp.cmp(range_4, img_dir / 'custom-basename-4.png')
    assert filecmp.cmp(range_4, img_dir / 'custom-basename-4-6_00.png')
    assert filecmp.cmp(range_6, img_dir / 'custom-basename-4-6_01.png')

    # Modify the included plot
    contents = (tmp_path / 'included_plot_21.rst').read_bytes()
    contents = contents.replace(b'plt.plot(range(6))', b'plt.plot(range(4))')
    (tmp_path / 'included_plot_21.rst').write_bytes(contents)
    # Build the pages again and check that the modified file was updated
    modification_times = [plot_directive_file(i).stat().st_mtime
                          for i in (1, 2, 3, 5)]
    build_sphinx_html(tmp_path, doctree_dir, html_dir)
    assert filecmp.cmp(range_4, plot_file(17))
    # Check that the plots in the plot_directive folder weren't changed.
    # (plot_directive_file(1) won't be modified, but it will be copied to html/
    # upon compilation, so plot_file(1) will be modified)
    assert plot_directive_file(1).stat().st_mtime == modification_times[0]
    assert plot_directive_file(2).stat().st_mtime == modification_times[1]
    assert plot_directive_file(3).stat().st_mtime == modification_times[2]
    assert filecmp.cmp(range_10, plot_file(1))
    assert filecmp.cmp(range_6, plot_file(2))
    assert filecmp.cmp(range_4, plot_file(3))
    # Make sure that figures marked with context are re-created (but that the
    # contents are the same)
    assert plot_directive_file(5).stat().st_mtime > modification_times[3]
    assert filecmp.cmp(range_6, plot_file(5))


def test_plot_html_show_source_link(tmp_path):
    shutil.copyfile(tinypages / 'conf.py', tmp_path / 'conf.py')
    shutil.copytree(tinypages / '_static', tmp_path / '_static')
    doctree_dir = tmp_path / 'doctrees'
    (tmp_path / 'index.rst').write_text("""
.. plot::

    plt.plot(range(2))
""")
    # Make sure source scripts are created by default
    html_dir1 = tmp_path / '_build' / 'html1'
    build_sphinx_html(tmp_path, doctree_dir, html_dir1)
    assert len(list(html_dir1.glob("**/index-1.py"))) == 1
    # Make sure source scripts are NOT created when
    # plot_html_show_source_link` is False
    html_dir2 = tmp_path / '_build' / 'html2'
    build_sphinx_html(tmp_path, doctree_dir, html_dir2,
                      extra_args=['-D', 'plot_html_show_source_link=0'])
    assert len(list(html_dir2.glob("**/index-1.py"))) == 0


@pytest.mark.parametrize('plot_html_show_source_link', [0, 1])
def test_show_source_link_true(tmp_path, plot_html_show_source_link):
    # Test that a source link is generated if :show-source-link: is true,
    # whether or not plot_html_show_source_link is true.
    shutil.copyfile(tinypages / 'conf.py', tmp_path / 'conf.py')
    shutil.copytree(tinypages / '_static', tmp_path / '_static')
    doctree_dir = tmp_path / 'doctrees'
    (tmp_path / 'index.rst').write_text("""
.. plot::
    :show-source-link: true

    plt.plot(range(2))
""")
    html_dir = tmp_path / '_build' / 'html'
    build_sphinx_html(tmp_path, doctree_dir, html_dir, extra_args=[
        '-D', f'plot_html_show_source_link={plot_html_show_source_link}'])
    assert len(list(html_dir.glob("**/index-1.py"))) == 1


@pytest.mark.parametrize('plot_html_show_source_link', [0, 1])
def test_show_source_link_false(tmp_path, plot_html_show_source_link):
    # Test that a source link is NOT generated if :show-source-link: is false,
    # whether or not plot_html_show_source_link is true.
    shutil.copyfile(tinypages / 'conf.py', tmp_path / 'conf.py')
    shutil.copytree(tinypages / '_static', tmp_path / '_static')
    doctree_dir = tmp_path / 'doctrees'
    (tmp_path / 'index.rst').write_text("""
.. plot::
    :show-source-link: false

    plt.plot(range(2))
""")
    html_dir = tmp_path / '_build' / 'html'
    build_sphinx_html(tmp_path, doctree_dir, html_dir, extra_args=[
        '-D', f'plot_html_show_source_link={plot_html_show_source_link}'])
    assert len(list(html_dir.glob("**/index-1.py"))) == 0


def test_plot_html_show_source_link_custom_basename(tmp_path):
    # Test that source link filename includes .py extension when using custom basename
    shutil.copyfile(tinypages / 'conf.py', tmp_path / 'conf.py')
    shutil.copytree(tinypages / '_static', tmp_path / '_static')
    doctree_dir = tmp_path / 'doctrees'
    (tmp_path / 'index.rst').write_text("""
.. plot::
    :filename-prefix: custom-name

    plt.plot(range(2))
""")
    html_dir = tmp_path / '_build' / 'html'
    build_sphinx_html(tmp_path, doctree_dir, html_dir)

    # Check that source file with .py extension is generated
    assert len(list(html_dir.glob("**/custom-name.py"))) == 1

    # Check that the HTML contains the correct link with .py extension
    html_content = (html_dir / 'index.html').read_text()
    assert 'custom-name.py' in html_content


def test_plot_html_code_caption(tmp_path):
    # Test that :code-caption: option adds caption to code block
    shutil.copyfile(tinypages / 'conf.py', tmp_path / 'conf.py')
    shutil.copytree(tinypages / '_static', tmp_path / '_static')
    doctree_dir = tmp_path / 'doctrees'
    (tmp_path / 'index.rst').write_text("""
.. plot::
    :include-source:
    :code-caption: Example plotting code

    import matplotlib.pyplot as plt
    plt.plot([1, 2, 3], [1, 4, 9])
""")
    html_dir = tmp_path / '_build' / 'html'
    build_sphinx_html(tmp_path, doctree_dir, html_dir)

    # Check that the HTML contains the code caption
    html_content = (html_dir / 'index.html').read_text(encoding='utf-8')
    assert 'Example plotting code' in html_content
    # Verify the caption is associated with the code block
    # (appears in a caption element)
    assert '<p class="caption"' in html_content or 'caption' in html_content.lower()


def test_srcset_version(tmp_path):
    shutil.copytree(tinypages, tmp_path, dirs_exist_ok=True,
                    ignore=shutil.ignore_patterns('_build', 'doctrees',
                                                   'plot_directive'))
    html_dir = tmp_path / '_build' / 'html'
    img_dir = html_dir / '_images'
    doctree_dir = tmp_path / 'doctrees'

    build_sphinx_html(tmp_path, doctree_dir, html_dir,
                      extra_args=['-D', 'plot_srcset=2x'])

    def plot_file(num, suff=''):
        return img_dir / f'some_plots-{num}{suff}.png'

    # check some-plots
    for ind in [1, 2, 3, 5, 7, 11, 13, 15, 17]:
        assert plot_file(ind).exists()
        assert plot_file(ind, suff='.2x').exists()

    assert (img_dir / 'nestedpage-index-1.png').exists()
    assert (img_dir / 'nestedpage-index-1.2x.png').exists()
    assert (img_dir / 'nestedpage-index-2.png').exists()
    assert (img_dir / 'nestedpage-index-2.2x.png').exists()
    assert (img_dir / 'nestedpage2-index-1.png').exists()
    assert (img_dir / 'nestedpage2-index-1.2x.png').exists()
    assert (img_dir / 'nestedpage2-index-2.png').exists()
    assert (img_dir / 'nestedpage2-index-2.2x.png').exists()

    # Check html for srcset

    assert ('srcset="_images/some_plots-1.png, _images/some_plots-1.2x.png 2.00x"'
            in (html_dir / 'some_plots.html').read_text(encoding='utf-8'))

    st = ('srcset="../_images/nestedpage-index-1.png, '
          '../_images/nestedpage-index-1.2x.png 2.00x"')
    assert st in (html_dir / 'nestedpage/index.html').read_text(encoding='utf-8')

    st = ('srcset="../_images/nestedpage2-index-2.png, '
          '../_images/nestedpage2-index-2.2x.png 2.00x"')
    assert st in (html_dir / 'nestedpage2/index.html').read_text(encoding='utf-8')
