"""
    test_apidoc
    ~~~~~~~~~~~

    Test the sphinx.apidoc module.

    :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS.
    :license: BSD, see LICENSE for details.
"""

from collections import namedtuple

import pytest

from sphinx.ext.apidoc import main as apidoc_main
from sphinx.testing.path import path


@pytest.fixture()
def apidoc(rootdir, tempdir, apidoc_params):
    _, kwargs = apidoc_params
    coderoot = rootdir / kwargs.get('coderoot', 'test-root')
    outdir = tempdir / 'out'
    excludes = [coderoot / e for e in kwargs.get('excludes', [])]
    args = ['-o', outdir, '-F', coderoot] + excludes + kwargs.get('options', [])
    apidoc_main(args)
    return namedtuple('apidoc', 'coderoot,outdir')(coderoot, outdir)


@pytest.fixture
def apidoc_params(request):
    if hasattr(request.node, 'iter_markers'):  # pytest-3.6.0 or newer
        markers = request.node.iter_markers("apidoc")
    else:
        markers = request.node.get_marker("apidoc")
    pargs = {}
    kwargs = {}

    if markers is not None:
        for info in reversed(list(markers)):
            for i, a in enumerate(info.args):
                pargs[i] = a
            kwargs.update(info.kwargs)

    args = [pargs[i] for i in sorted(pargs.keys())]
    return args, kwargs


@pytest.mark.apidoc(coderoot='test-root')
def test_simple(make_app, apidoc):
    outdir = apidoc.outdir
    assert (outdir / 'conf.py').isfile()
    assert (outdir / 'index.rst').isfile()

    app = make_app('text', srcdir=outdir)
    app.build()
    print(app._status.getvalue())
    print(app._warning.getvalue())


@pytest.mark.apidoc(
    coderoot='test-apidoc-pep420/a',
    options=["--implicit-namespaces"],
)
def test_pep_0420_enabled(make_app, apidoc):
    outdir = apidoc.outdir
    assert (outdir / 'conf.py').isfile()
    assert (outdir / 'a.b.c.rst').isfile()
    assert (outdir / 'a.b.e.rst').isfile()
    assert (outdir / 'a.b.x.rst').isfile()

    with open(outdir / 'a.b.c.rst') as f:
        rst = f.read()
        assert "automodule:: a.b.c.d\n" in rst
        assert "automodule:: a.b.c\n" in rst

    with open(outdir / 'a.b.e.rst') as f:
        rst = f.read()
        assert "automodule:: a.b.e.f\n" in rst

    with open(outdir / 'a.b.x.rst') as f:
        rst = f.read()
        assert "automodule:: a.b.x.y\n" in rst
        assert "automodule:: a.b.x\n" not in rst

    app = make_app('text', srcdir=outdir)
    app.build()
    print(app._status.getvalue())
    print(app._warning.getvalue())

    builddir = outdir / '_build' / 'text'
    assert (builddir / 'a.b.c.txt').isfile()
    assert (builddir / 'a.b.e.txt').isfile()
    assert (builddir / 'a.b.x.txt').isfile()

    with open(builddir / 'a.b.c.txt') as f:
        txt = f.read()
        assert "a.b.c package\n" in txt

    with open(builddir / 'a.b.e.txt') as f:
        txt = f.read()
        assert "a.b.e.f module\n" in txt

    with open(builddir / 'a.b.x.txt') as f:
        txt = f.read()
        assert "a.b.x namespace\n" in txt


@pytest.mark.apidoc(
    coderoot='test-apidoc-pep420/a',
    options=["--implicit-namespaces", "--separate"],
)
def test_pep_0420_enabled_separate(make_app, apidoc):
    outdir = apidoc.outdir
    assert (outdir / 'conf.py').isfile()
    assert (outdir / 'a.b.c.rst').isfile()
    assert (outdir / 'a.b.e.rst').isfile()
    assert (outdir / 'a.b.e.f.rst').isfile()
    assert (outdir / 'a.b.x.rst').isfile()
    assert (outdir / 'a.b.x.y.rst').isfile()

    with open(outdir / 'a.b.c.rst') as f:
        rst = f.read()
        assert ".. toctree::\n   :maxdepth: 4\n\n   a.b.c.d\n" in rst

    with open(outdir / 'a.b.e.rst') as f:
        rst = f.read()
        assert ".. toctree::\n   :maxdepth: 4\n\n   a.b.e.f\n" in rst

    with open(outdir / 'a.b.x.rst') as f:
        rst = f.read()
        assert ".. toctree::\n   :maxdepth: 4\n\n   a.b.x.y\n" in rst

    app = make_app('text', srcdir=outdir)
    app.build()
    print(app._status.getvalue())
    print(app._warning.getvalue())

    builddir = outdir / '_build' / 'text'
    assert (builddir / 'a.b.c.txt').isfile()
    assert (builddir / 'a.b.e.txt').isfile()
    assert (builddir / 'a.b.e.f.txt').isfile()
    assert (builddir / 'a.b.x.txt').isfile()
    assert (builddir / 'a.b.x.y.txt').isfile()

    with open(builddir / 'a.b.c.txt') as f:
        txt = f.read()
        assert "a.b.c package\n" in txt

    with open(builddir / 'a.b.e.f.txt') as f:
        txt = f.read()
        assert "a.b.e.f module\n" in txt

    with open(builddir / 'a.b.x.txt') as f:
        txt = f.read()
        assert "a.b.x namespace\n" in txt


@pytest.mark.apidoc(coderoot='test-apidoc-pep420/a')
def test_pep_0420_disabled(make_app, apidoc):
    outdir = apidoc.outdir
    assert (outdir / 'conf.py').isfile()
    assert not (outdir / 'a.b.c.rst').exists()
    assert not (outdir / 'a.b.x.rst').exists()

    app = make_app('text', srcdir=outdir)
    app.build()
    print(app._status.getvalue())
    print(app._warning.getvalue())


@pytest.mark.apidoc(
    coderoot='test-apidoc-pep420/a/b')
def test_pep_0420_disabled_top_level_verify(make_app, apidoc):
    outdir = apidoc.outdir
    assert (outdir / 'conf.py').isfile()
    assert (outdir / 'c.rst').isfile()
    assert not (outdir / 'x.rst').exists()

    with open(outdir / 'c.rst') as f:
        rst = f.read()
        assert "c package\n" in rst
        assert "automodule:: c.d\n" in rst
        assert "automodule:: c\n" in rst

    app = make_app('text', srcdir=outdir)
    app.build()
    print(app._status.getvalue())
    print(app._warning.getvalue())


@pytest.mark.apidoc(
    coderoot='test-apidoc-trailing-underscore')
def test_trailing_underscore(make_app, apidoc):
    outdir = apidoc.outdir
    assert (outdir / 'conf.py').isfile()
    assert (outdir / 'package_.rst').isfile()

    app = make_app('text', srcdir=outdir)
    app.build()
    print(app._status.getvalue())
    print(app._warning.getvalue())

    builddir = outdir / '_build' / 'text'
    with open(builddir / 'package_.txt') as f:
        rst = f.read()
        assert "package_ package\n" in rst
        assert "package_.module_ module\n" in rst


@pytest.mark.apidoc(
    coderoot='test-apidoc-pep420/a',
    excludes=["b/c/d.py", "b/e/f.py", "b/e/__init__.py"],
    options=["--implicit-namespaces", "--separate"],
)
def test_excludes(apidoc):
    outdir = apidoc.outdir
    assert (outdir / 'conf.py').isfile()
    assert (outdir / 'a.rst').isfile()
    assert (outdir / 'a.b.rst').isfile()
    assert (outdir / 'a.b.c.rst').isfile()  # generated because not empty
    assert not (outdir / 'a.b.e.rst').isfile()  # skipped because of empty after excludes
    assert (outdir / 'a.b.x.rst').isfile()
    assert (outdir / 'a.b.x.y.rst').isfile()


@pytest.mark.apidoc(
    coderoot='test-apidoc-pep420/a',
    excludes=["b/e"],
    options=["--implicit-namespaces", "--separate"],
)
def test_excludes_subpackage_should_be_skipped(apidoc):
    """Subpackage exclusion should work."""
    outdir = apidoc.outdir
    assert (outdir / 'conf.py').isfile()
    assert (outdir / 'a.rst').isfile()
    assert (outdir / 'a.b.rst').isfile()
    assert (outdir / 'a.b.c.rst').isfile()  # generated because not empty
    assert not (outdir / 'a.b.e.f.rst').isfile()  # skipped because 'b/e' subpackage is skipped


@pytest.mark.apidoc(
    coderoot='test-apidoc-pep420/a',
    excludes=["b/e/f.py"],
    options=["--implicit-namespaces", "--separate"],
)
def test_excludes_module_should_be_skipped(apidoc):
    """Module exclusion should work."""
    outdir = apidoc.outdir
    assert (outdir / 'conf.py').isfile()
    assert (outdir / 'a.rst').isfile()
    assert (outdir / 'a.b.rst').isfile()
    assert (outdir / 'a.b.c.rst').isfile()  # generated because not empty
    assert not (outdir / 'a.b.e.f.rst').isfile()  # skipped because of empty after excludes


@pytest.mark.apidoc(
    coderoot='test-apidoc-pep420/a',
    excludes=[],
    options=["--implicit-namespaces", "--separate"],
)
def test_excludes_module_should_not_be_skipped(apidoc):
    """Module should be included if no excludes are used."""
    outdir = apidoc.outdir
    assert (outdir / 'conf.py').isfile()
    assert (outdir / 'a.rst').isfile()
    assert (outdir / 'a.b.rst').isfile()
    assert (outdir / 'a.b.c.rst').isfile()  # generated because not empty
    assert (outdir / 'a.b.e.f.rst').isfile()  # skipped because of empty after excludes


@pytest.mark.apidoc(
    coderoot='test-root',
    options=[
        '--doc-project', 'プロジェクト名',
        '--doc-author', '著者名',
        '--doc-version', 'バージョン',
        '--doc-release', 'リリース',
    ],
)
def test_multibyte_parameters(make_app, apidoc):
    outdir = apidoc.outdir
    assert (outdir / 'conf.py').isfile()
    assert (outdir / 'index.rst').isfile()

    conf_py = (outdir / 'conf.py').read_text()
    assert "project = 'プロジェクト名'" in conf_py
    assert "author = '著者名'" in conf_py
    assert "version = 'バージョン'" in conf_py
    assert "release = 'リリース'" in conf_py

    app = make_app('text', srcdir=outdir)
    app.build()
    print(app._status.getvalue())
    print(app._warning.getvalue())


@pytest.mark.apidoc(
    coderoot='test-root',
    options=['--ext-mathjax'],
)
def test_extension_parsed(make_app, apidoc):
    outdir = apidoc.outdir
    assert (outdir / 'conf.py').isfile()

    with open(outdir / 'conf.py') as f:
        rst = f.read()
        assert "sphinx.ext.mathjax" in rst


@pytest.mark.apidoc(
    coderoot='test-apidoc-toc/mypackage',
    options=["--implicit-namespaces"],
)
def test_toc_all_references_should_exist_pep420_enabled(make_app, apidoc):
    """All references in toc should exist. This test doesn't say if
       directories with empty __init__.py and and nothing else should be
       skipped, just ensures consistency between what's referenced in the toc
       and what is created. This is the variant with pep420 enabled.
    """
    outdir = apidoc.outdir
    assert (outdir / 'conf.py').isfile()

    toc = extract_toc(outdir / 'mypackage.rst')

    refs = [l.strip() for l in toc.splitlines() if l.strip()]
    found_refs = []
    missing_files = []
    for ref in refs:
        if ref and ref[0] in (':', '#'):
            continue
        found_refs.append(ref)
        filename = "{}.rst".format(ref)
        if not (outdir / filename).isfile():
            missing_files.append(filename)

    assert len(missing_files) == 0, \
        'File(s) referenced in TOC not found: {}\n' \
        'TOC:\n{}'.format(", ".join(missing_files), toc)


@pytest.mark.apidoc(
    coderoot='test-apidoc-toc/mypackage',
)
def test_toc_all_references_should_exist_pep420_disabled(make_app, apidoc):
    """All references in toc should exist. This test doesn't say if
       directories with empty __init__.py and and nothing else should be
       skipped, just ensures consistency between what's referenced in the toc
       and what is created. This is the variant with pep420 disabled.
    """
    outdir = apidoc.outdir
    assert (outdir / 'conf.py').isfile()

    toc = extract_toc(outdir / 'mypackage.rst')

    refs = [l.strip() for l in toc.splitlines() if l.strip()]
    found_refs = []
    missing_files = []
    for ref in refs:
        if ref and ref[0] in (':', '#'):
            continue
        filename = "{}.rst".format(ref)
        found_refs.append(ref)
        if not (outdir / filename).isfile():
            missing_files.append(filename)

    assert len(missing_files) == 0, \
        'File(s) referenced in TOC not found: {}\n' \
        'TOC:\n{}'.format(", ".join(missing_files), toc)


def extract_toc(path):
    """Helper: Extract toc section from package rst file"""
    with open(path) as f:
        rst = f.read()

    # Read out the part containing the toctree
    toctree_start = "\n.. toctree::\n"
    toctree_end = "\nSubmodules"

    start_idx = rst.index(toctree_start)
    end_idx = rst.index(toctree_end, start_idx)
    toctree = rst[start_idx + len(toctree_start):end_idx]

    return toctree


@pytest.mark.apidoc(
    coderoot='test-apidoc-subpackage-in-toc',
    options=['--separate']
)
def test_subpackage_in_toc(make_app, apidoc):
    """Make sure that empty subpackages with non-empty subpackages in them
       are not skipped (issue #4520)
    """
    outdir = apidoc.outdir
    assert (outdir / 'conf.py').isfile()

    assert (outdir / 'parent.rst').isfile()
    with open(outdir / 'parent.rst') as f:
        parent = f.read()
    assert 'parent.child' in parent

    assert (outdir / 'parent.child.rst').isfile()
    with open(outdir / 'parent.child.rst') as f:
        parent_child = f.read()
    assert 'parent.child.foo' in parent_child

    assert (outdir / 'parent.child.foo.rst').isfile()


def test_private(tempdir):
    (tempdir / 'hello.py').write_text('')
    (tempdir / '_world.py').write_text('')

    # without --private option
    apidoc_main(['-o', tempdir, tempdir])
    assert (tempdir / 'hello.rst').exists()
    assert ':private-members:' not in (tempdir / 'hello.rst').read_text()
    assert not (tempdir / '_world.rst').exists()

    # with --private option
    apidoc_main(['--private', '-f', '-o', tempdir, tempdir])
    assert (tempdir / 'hello.rst').exists()
    assert ':private-members:' in (tempdir / 'hello.rst').read_text()
    assert (tempdir / '_world.rst').exists()


def test_toc_file(tempdir):
    outdir = path(tempdir)
    (outdir / 'module').makedirs()
    (outdir / 'example.py').write_text('')
    (outdir / 'module' / 'example.py').write_text('')
    apidoc_main(['-o', tempdir, tempdir])
    assert (outdir / 'modules.rst').exists()

    content = (outdir / 'modules.rst').read_text()
    assert content == ("test_toc_file0\n"
                       "==============\n"
                       "\n"
                       ".. toctree::\n"
                       "   :maxdepth: 4\n"
                       "\n"
                       "   example\n")


def test_module_file(tempdir):
    outdir = path(tempdir)
    (outdir / 'example.py').write_text('')
    apidoc_main(['-o', tempdir, tempdir])
    assert (outdir / 'example.rst').exists()

    content = (outdir / 'example.rst').read_text()
    assert content == ("example module\n"
                       "==============\n"
                       "\n"
                       ".. automodule:: example\n"
                       "   :members:\n"
                       "   :undoc-members:\n"
                       "   :show-inheritance:\n")


def test_module_file_noheadings(tempdir):
    outdir = path(tempdir)
    (outdir / 'example.py').write_text('')
    apidoc_main(['--no-headings', '-o', tempdir, tempdir])
    assert (outdir / 'example.rst').exists()

    content = (outdir / 'example.rst').read_text()
    assert content == (".. automodule:: example\n"
                       "   :members:\n"
                       "   :undoc-members:\n"
                       "   :show-inheritance:\n")


def test_package_file(tempdir):
    outdir = path(tempdir)
    (outdir / 'testpkg').makedirs()
    (outdir / 'testpkg' / '__init__.py').write_text('')
    (outdir / 'testpkg' / 'hello.py').write_text('')
    (outdir / 'testpkg' / 'world.py').write_text('')
    (outdir / 'testpkg' / 'subpkg').makedirs()
    (outdir / 'testpkg' / 'subpkg' / '__init__.py').write_text('')
    apidoc_main(['-o', tempdir, tempdir / 'testpkg'])
    assert (outdir / 'testpkg.rst').exists()
    assert (outdir / 'testpkg.subpkg.rst').exists()

    content = (outdir / 'testpkg.rst').read_text()
    assert content == ("testpkg package\n"
                       "===============\n"
                       "\n"
                       "Subpackages\n"
                       "-----------\n"
                       "\n"
                       ".. toctree::\n"
                       "   :maxdepth: 4\n"
                       "\n"
                       "   testpkg.subpkg\n"
                       "\n"
                       "Submodules\n"
                       "----------\n"
                       "\n"
                       "testpkg.hello module\n"
                       "--------------------\n"
                       "\n"
                       ".. automodule:: testpkg.hello\n"
                       "   :members:\n"
                       "   :undoc-members:\n"
                       "   :show-inheritance:\n"
                       "\n"
                       "testpkg.world module\n"
                       "--------------------\n"
                       "\n"
                       ".. automodule:: testpkg.world\n"
                       "   :members:\n"
                       "   :undoc-members:\n"
                       "   :show-inheritance:\n"
                       "\n"
                       "Module contents\n"
                       "---------------\n"
                       "\n"
                       ".. automodule:: testpkg\n"
                       "   :members:\n"
                       "   :undoc-members:\n"
                       "   :show-inheritance:\n")

    content = (outdir / 'testpkg.subpkg.rst').read_text()
    assert content == ("testpkg.subpkg package\n"
                       "======================\n"
                       "\n"
                       "Module contents\n"
                       "---------------\n"
                       "\n"
                       ".. automodule:: testpkg.subpkg\n"
                       "   :members:\n"
                       "   :undoc-members:\n"
                       "   :show-inheritance:\n")


def test_package_file_separate(tempdir):
    outdir = path(tempdir)
    (outdir / 'testpkg').makedirs()
    (outdir / 'testpkg' / '__init__.py').write_text('')
    (outdir / 'testpkg' / 'example.py').write_text('')
    apidoc_main(['--separate', '-o', tempdir, tempdir / 'testpkg'])
    assert (outdir / 'testpkg.rst').exists()
    assert (outdir / 'testpkg.example.rst').exists()

    content = (outdir / 'testpkg.rst').read_text()
    assert content == ("testpkg package\n"
                       "===============\n"
                       "\n"
                       "Submodules\n"
                       "----------\n"
                       "\n"
                       ".. toctree::\n"
                       "   :maxdepth: 4\n"
                       "\n"
                       "   testpkg.example\n"
                       "\n"
                       "Module contents\n"
                       "---------------\n"
                       "\n"
                       ".. automodule:: testpkg\n"
                       "   :members:\n"
                       "   :undoc-members:\n"
                       "   :show-inheritance:\n")

    content = (outdir / 'testpkg.example.rst').read_text()
    assert content == ("testpkg.example module\n"
                       "======================\n"
                       "\n"
                       ".. automodule:: testpkg.example\n"
                       "   :members:\n"
                       "   :undoc-members:\n"
                       "   :show-inheritance:\n")


def test_package_file_module_first(tempdir):
    outdir = path(tempdir)
    (outdir / 'testpkg').makedirs()
    (outdir / 'testpkg' / '__init__.py').write_text('')
    (outdir / 'testpkg' / 'example.py').write_text('')
    apidoc_main(['--module-first', '-o', tempdir, tempdir])

    content = (outdir / 'testpkg.rst').read_text()
    assert content == ("testpkg package\n"
                       "===============\n"
                       "\n"
                       ".. automodule:: testpkg\n"
                       "   :members:\n"
                       "   :undoc-members:\n"
                       "   :show-inheritance:\n"
                       "\n"
                       "Submodules\n"
                       "----------\n"
                       "\n"
                       "testpkg.example module\n"
                       "----------------------\n"
                       "\n"
                       ".. automodule:: testpkg.example\n"
                       "   :members:\n"
                       "   :undoc-members:\n"
                       "   :show-inheritance:\n")


def test_package_file_without_submodules(tempdir):
    outdir = path(tempdir)
    (outdir / 'testpkg').makedirs()
    (outdir / 'testpkg' / '__init__.py').write_text('')
    apidoc_main(['-o', tempdir, tempdir / 'testpkg'])
    assert (outdir / 'testpkg.rst').exists()

    content = (outdir / 'testpkg.rst').read_text()
    assert content == ("testpkg package\n"
                       "===============\n"
                       "\n"
                       "Module contents\n"
                       "---------------\n"
                       "\n"
                       ".. automodule:: testpkg\n"
                       "   :members:\n"
                       "   :undoc-members:\n"
                       "   :show-inheritance:\n")


def test_namespace_package_file(tempdir):
    outdir = path(tempdir)
    (outdir / 'testpkg').makedirs()
    (outdir / 'testpkg' / 'example.py').write_text('')
    apidoc_main(['--implicit-namespace', '-o', tempdir, tempdir / 'testpkg'])
    assert (outdir / 'testpkg.rst').exists()

    content = (outdir / 'testpkg.rst').read_text()
    assert content == ("testpkg namespace\n"
                       "=================\n"
                       "\n"
                       "Submodules\n"
                       "----------\n"
                       "\n"
                       "testpkg.example module\n"
                       "----------------------\n"
                       "\n"
                       ".. automodule:: testpkg.example\n"
                       "   :members:\n"
                       "   :undoc-members:\n"
                       "   :show-inheritance:\n")
