import io
import os
import pathlib
import re
import shutil
import sys
from unittest.mock import patch, Mock, call

import pytest
import sphinx
from sphinx.application import Sphinx
import sphinx.util.logging

from autoapi.mappers.python import (
    PythonModule,
    PythonFunction,
    PythonClass,
    PythonData,
    PythonMethod,
)
import autoapi.settings


def rebuild(confoverrides=None, confdir=".", **kwargs):
    app = Sphinx(
        srcdir=".",
        confdir=confdir,
        outdir="_build/text",
        doctreedir="_build/.doctrees",
        buildername="text",
        confoverrides=confoverrides,
        **kwargs
    )
    app.build()


@pytest.fixture(scope="class")
def builder():
    cwd = os.getcwd()

    def build(test_dir, confoverrides=None, **kwargs):
        os.chdir("tests/python/{0}".format(test_dir))
        rebuild(confoverrides=confoverrides, **kwargs)

    yield build

    try:
        shutil.rmtree("_build")
    finally:
        os.chdir(cwd)


class TestSimpleModule:
    @pytest.fixture(autouse=True, scope="class")
    def built(self, builder):
        builder(
            "pyexample",
            warningiserror=True,
            confoverrides={"suppress_warnings": ["app"]},
        )

    def test_integration(self):
        self.check_integration("_build/text/autoapi/example/index.txt")

    def test_manual_directives(self):
        example_path = "_build/text/manualapi.txt"
        # The manual directives should contain the same information
        self.check_integration(example_path)

        with io.open(example_path, encoding="utf8") as example_handle:
            example_file = example_handle.read()

        assert "@example.decorator_okay" in example_file

    def check_integration(self, example_path):
        with io.open(example_path, encoding="utf8") as example_handle:
            example_file = example_handle.read()

        assert "class example.Foo" in example_file
        assert "class Meta" in example_file
        assert "attr2" in example_file
        assert "This is the docstring of an instance attribute." in example_file
        assert "method_okay(self, foo=None, bar=None)" in example_file
        assert "method_multiline(self, foo=None, bar=None, baz=None)" in example_file
        assert "method_tricky(self, foo=None, bar=dict(foo=1, bar=2))" in example_file

        # Are constructor arguments from the class docstring parsed?
        assert "Set an attribute" in example_file

        # "self" should not be included in constructor arguments
        assert "Foo(self" not in example_file

        # Overridden methods without their own docstring
        # should inherit the parent's docstring
        assert example_file.count("This method should parse okay") == 2

        assert not os.path.exists("_build/text/autoapi/method_multiline")

        # Inherited constructor docstrings should be included in a merged
        # (autoapi_python_class_content="both") class docstring only once.
        assert example_file.count("One __init__.") == 3

        index_path = "_build/text/index.txt"
        with io.open(index_path, encoding="utf8") as index_handle:
            index_file = index_handle.read()

        assert "API Reference" in index_file

        assert "Foo" in index_file
        assert "Meta" in index_file

    def test_napoleon_integration_not_loaded(self, builder):
        example_path = "_build/text/autoapi/example/index.txt"
        with io.open(example_path, encoding="utf8") as example_handle:
            example_file = example_handle.read()

        # Check that docstrings are not transformed without napoleon loaded
        assert "Args" in example_file

        assert "Returns" in example_file

    def test_show_inheritance(self, builder):
        example_path = "_build/text/autoapi/example/index.txt"
        with io.open(example_path, encoding="utf8") as example_handle:
            example_file = example_handle.read()

        assert "Bases:" in example_file


class TestMovedConfPy(TestSimpleModule):
    @pytest.fixture(autouse=True, scope="class")
    def built(self, builder):
        builder(
            "pymovedconfpy",
            confdir="confpy",
            warningiserror=True,
            confoverrides={"suppress_warnings": ["app"]},
        )


class TestSimpleModuleDifferentPrimaryDomain:
    @pytest.fixture(autouse=True, scope="class")
    def built(self, builder):
        builder(
            "pyexample",
            warningiserror=True,
            confoverrides={
                "autoapi_options": [
                    "members",
                    "undoc-members",
                    "private-members",
                    "special-members",
                    "imported-members",
                ],
                "primary_domain": "cpp",
                "suppress_warnings": ["app"],
            },
        )

    def test_success(self):
        pass


class TestSimpleStubModule:
    @pytest.fixture(autouse=True, scope="class")
    def built(self, builder):
        builder("pyiexample")

    def test_integration(self):
        example_path = "_build/text/autoapi/example/index.txt"
        with io.open(example_path, encoding="utf8") as example_handle:
            example_file = example_handle.read()

        # Are pyi files preferred
        assert "DoNotFindThis" not in example_file

        assert "class example.Foo" in example_file
        assert "class Meta" in example_file
        assert "Another class var docstring" in example_file
        assert "A class var without a value." in example_file
        assert "method_okay(self, foo=None, bar=None)" in example_file
        assert "method_multiline(self, foo=None, bar=None, baz=None)" in example_file
        assert "method_without_docstring(self)" in example_file

        # Are constructor arguments from the class docstring parsed?
        assert "Set an attribute" in example_file


class TestSimpleStubModuleNotPreferred:
    @pytest.fixture(autouse=True, scope="class")
    def built(self, builder):
        builder("pyiexample2")

    def test_integration(self):
        example_path = "_build/text/autoapi/example/index.txt"
        with io.open(example_path, encoding="utf8") as example_handle:
            example_file = example_handle.read()

        # Are py files preferred
        assert "DoNotFindThis" not in example_file

        assert "Foo" in example_file


class TestPy3Module:
    @pytest.fixture(autouse=True, scope="class")
    def built(self, builder):
        builder("py3example")

    def test_integration(self):
        example_path = "_build/text/autoapi/example/index.txt"
        with io.open(example_path, encoding="utf8") as example_handle:
            example_file = example_handle.read()

        assert "Initialize self" not in example_file
        assert "a new type" not in example_file

    def test_annotations(self):
        example_path = "_build/text/autoapi/example/index.txt"
        with io.open(example_path, encoding="utf8") as example_handle:
            example_file = example_handle.read()

        assert "software = sphinx" in example_file
        assert "code_snippet = Multiline-String" in example_file

        assert "max_rating :int = 10" in example_file
        assert "is_valid" in example_file

        assert "ratings" in example_file
        assert "List[int]" in example_file

        assert "Dict[int, str]" in example_file

        assert "start: int" in example_file
        assert "Iterable[int]" in example_file

        assert "List[Union[str, int]]" in example_file

        assert "not_yet_a: A" in example_file
        assert "imported: example2.B" in example_file
        assert "-> example2.B" in example_file

        assert "is_an_a" in example_file
        assert "ClassVar" in example_file

        assert "instance_var" in example_file

        assert "global_a :A" in example_file

        assert "my_method(self) -> str" in example_file

        assert "class example.SomeMetaclass" in example_file

    def test_overload(self):
        example_path = "_build/text/autoapi/example/index.txt"
        with io.open(example_path, encoding="utf8") as example_handle:
            example_file = example_handle.read()

        assert "overloaded_func(a: float" in example_file
        assert "overloaded_func(a: str" in example_file
        assert "overloaded_func(a: Union" not in example_file
        assert "Overloaded function" in example_file

        assert "overloaded_method(self, a: float" in example_file
        assert "overloaded_method(self, a: str" in example_file
        assert "overloaded_method(self, a: Union" not in example_file
        assert "Overloaded method" in example_file

        assert "overloaded_class_method(cls, a: float" in example_file
        assert "overloaded_class_method(cls, a: str" in example_file
        assert "overloaded_class_method(cls, a: Union" not in example_file
        assert "Overloaded method" in example_file

        assert "undoc_overloaded_func" in example_file
        assert "undoc_overloaded_method" in example_file

        assert "C(a: int" in example_file
        assert "C(a: float" in example_file
        assert "C(a: str" not in example_file
        assert "C(self, a: int" not in example_file
        assert "C(self, a: float" not in example_file
        assert "C(self, a: str" not in example_file

    def test_async(self):
        example_path = "_build/text/autoapi/example/index.txt"
        with io.open(example_path, encoding="utf8") as example_handle:
            example_file = example_handle.read()

        assert "async async_method" in example_file
        assert "async example.async_function" in example_file


def test_py3_hiding_undoc_overloaded_members(builder):
    confoverrides = {"autoapi_options": ["members", "special-members"]}
    builder("py3example", confoverrides=confoverrides)

    example_path = "_build/text/autoapi/example/index.txt"
    with io.open(example_path, encoding="utf8") as example_handle:
        example_file = example_handle.read()

    assert "overloaded_func" in example_file
    assert "overloaded_method" in example_file
    assert "undoc_overloaded_func" not in example_file
    assert "undoc_overloaded_method" not in example_file


class TestAnnotationCommentsModule:
    @pytest.fixture(autouse=True, scope="class")
    def built(self, builder):
        builder("pyannotationcommentsexample")

    def test_integration(self):
        example_path = "_build/text/autoapi/example/index.txt"
        with io.open(example_path, encoding="utf8") as example_handle:
            example_file = example_handle.read()

        assert "max_rating :int = 10" in example_file

        assert "ratings" in example_file
        assert "List[int]" in example_file

        assert "Dict[int, str]" in example_file

        # When astroid>2.2.5
        # assert "start: int" in example_file
        # assert "end: int" in example_file
        assert "Iterable[int]" in example_file

        assert "List[Union[str, int]]" in example_file

        assert "not_yet_a: A" in example_file
        assert "is_an_a" in example_file
        assert "ClassVar" in example_file

        assert "instance_var" in example_file

        assert "global_a :A" in example_file


@pytest.mark.skipif(
    sys.version_info < (3, 8), reason="Positional only arguments need Python >=3.8"
)
class TestPositionalOnlyArgumentsModule:
    @pytest.fixture(autouse=True, scope="class")
    def built(self, builder):
        builder("py38positionalparams")

    def test_integration(self):
        example_path = "_build/text/autoapi/example/index.txt"
        with io.open(example_path, encoding="utf8") as example_handle:
            example_file = example_handle.read()

        assert "f_simple(a, b, /, c, d, *, e, f)" in example_file

        assert (
            "f_comment(a: int, b: int, /, c: Optional[int], d: Optional[int], *, e: float, f: float)"
            in example_file
        )
        assert (
            "f_annotation(a: int, b: int, /, c: Optional[int], d: Optional[int], *, e: float, f: float)"
            in example_file
        )
        assert (
            "f_arg_comment(a: int, b: int, /, c: Optional[int], d: Optional[int], *, e: float, f: float)"
            in example_file
        )
        assert "f_no_cd(a: int, b: int, /, *, e: float, f: float)" in example_file


def test_napoleon_integration_loaded(builder):
    confoverrides = {
        "extensions": ["autoapi.extension", "sphinx.ext.autodoc", "sphinx.ext.napoleon"]
    }

    builder("pyexample", confoverrides=confoverrides)

    example_path = "_build/text/autoapi/example/index.txt"
    with io.open(example_path, encoding="utf8") as example_handle:
        example_file = example_handle.read()

    assert "Parameters" in example_file

    assert "Return type" in example_file

    assert "Args" not in example_file


class TestSimplePackage:
    @pytest.fixture(autouse=True, scope="class")
    def built(self, builder):
        builder("pypackageexample")

    def test_integration_with_package(self):

        example_path = "_build/text/autoapi/example/index.txt"
        with io.open(example_path, encoding="utf8") as example_handle:
            example_file = example_handle.read()

        assert "example.foo" in example_file
        assert "example.module_level_method(foo, bar)" in example_file

        example_foo_path = "_build/text/autoapi/example/foo/index.txt"
        with io.open(example_foo_path, encoding="utf8") as example_foo_handle:
            example_foo_file = example_foo_handle.read()

        assert "class example.foo.Foo" in example_foo_file
        assert "method_okay(self, foo=None, bar=None)" in example_foo_file

        index_path = "_build/text/index.txt"
        with io.open(index_path, encoding="utf8") as index_handle:
            index_file = index_handle.read()

        assert "API Reference" in index_file
        assert "example.foo" in index_file
        assert "Foo" in index_file
        assert "module_level_method" in index_file


def test_simple_no_false_warnings(builder, caplog):
    logger = sphinx.util.logging.getLogger("autoapi")
    logger.logger.addHandler(caplog.handler)
    builder("pypackageexample")

    assert "Cannot resolve" not in caplog.text


def _test_class_content(builder, class_content):
    confoverrides = {"autoapi_python_class_content": class_content}

    builder("pyexample", confoverrides=confoverrides)

    example_path = "_build/text/autoapi/example/index.txt"
    with io.open(example_path, encoding="utf8") as example_handle:
        example_file = example_handle.read()

        if class_content == "init":
            assert "Can we parse arguments" not in example_file
        else:
            assert "Can we parse arguments" in example_file

        if class_content not in ("both", "init"):
            assert "Constructor docstring" not in example_file
        else:
            assert "Constructor docstring" in example_file


def test_class_class_content(builder):
    _test_class_content(builder, "class")


def test_both_class_content(builder):
    _test_class_content(builder, "both")


def test_init_class_content(builder):
    _test_class_content(builder, "init")


def test_hiding_private_members(builder):
    confoverrides = {"autoapi_options": ["members", "undoc-members", "special-members"]}
    builder("pypackageexample", confoverrides=confoverrides)

    example_path = "_build/text/autoapi/example/index.txt"
    with io.open(example_path, encoding="utf8") as example_handle:
        example_file = example_handle.read()

    assert "private" not in example_file

    private_path = "_build/text/autoapi/example/_private_module/index.txt"
    with io.open(private_path, encoding="utf8") as private_handle:
        private_file = private_handle.read()

    assert "public_method" in private_file


def test_hiding_inheritance(builder):
    confoverrides = {"autoapi_options": ["members", "undoc-members", "special-members"]}
    builder("pyexample", confoverrides=confoverrides)

    example_path = "_build/text/autoapi/example/index.txt"
    with io.open(example_path, encoding="utf8") as example_handle:
        example_file = example_handle.read()

    assert "Bases:" not in example_file


def test_hiding_imported_members(builder):
    confoverrides = {"autoapi_options": ["members", "undoc-members"]}
    builder("pypackagecomplex", confoverrides=confoverrides)

    subpackage_path = "_build/text/autoapi/complex/subpackage/index.txt"
    with io.open(subpackage_path, encoding="utf8") as subpackage_handle:
        subpackage_file = subpackage_handle.read()

    assert "Part of a public resolution chain." not in subpackage_file

    package_path = "_build/text/autoapi/complex/index.txt"
    with io.open(package_path, encoding="utf8") as package_handle:
        package_file = package_handle.read()

    assert "Part of a public resolution chain." not in package_file

    submodule_path = "_build/text/autoapi/complex/subpackage/submodule/index.txt"
    with io.open(submodule_path, encoding="utf8") as submodule_handle:
        submodule_file = submodule_handle.read()

    assert "A private function made public by import." not in submodule_file


def test_inherited_members(builder):
    confoverrides = {
        "autoapi_options": ["members", "inherited-members", "undoc-members"]
    }
    builder("pyexample", confoverrides=confoverrides)

    example_path = "_build/text/autoapi/example/index.txt"
    with io.open(example_path, encoding="utf8") as example_handle:
        example_file = example_handle.read()

    assert "class example.Bar" in example_file
    i = example_file.index("class example.Bar")
    assert "method_okay" in example_file[i:]


def test_skipping_members(builder):
    builder("pyskipexample")

    example_path = "_build/text/autoapi/example/index.txt"
    with io.open(example_path, encoding="utf8") as example_handle:
        example_file = example_handle.read()

    assert "foo doc" not in example_file
    assert "bar doc" not in example_file
    assert "bar m doc" not in example_file
    assert "baf doc" in example_file
    assert "baf m doc" not in example_file
    assert "baz doc" not in example_file
    assert "not ignored" in example_file


@pytest.mark.parametrize(
    "value,order",
    [
        ("bysource", [".Foo", ".decorator_okay", ".Bar"]),
        ("alphabetical", [".Bar", ".Foo", ".decorator_okay"]),
        ("groupwise", [".Bar", ".Foo", ".decorator_okay"]),
    ],
)
def test_order_members(builder, value, order):
    confoverrides = {"autoapi_member_order": value}
    builder("pyexample", confoverrides=confoverrides)

    example_path = "_build/text/autoapi/example/index.txt"
    with io.open(example_path, encoding="utf8") as example_handle:
        example_file = example_handle.read()

    indexes = [example_file.index(name) for name in order]
    assert indexes == sorted(indexes)


class _CompareInstanceType:
    def __init__(self, type_):
        self.type = type_

    def __eq__(self, other):
        return self.type is type(other)

    def __repr__(self):
        return "<expect type {}>".format(self.type.__name__)


def test_skip_members_hook(builder):
    emit_firstresult_patch = Mock(name="emit_firstresult_patch", return_value=False)
    with patch("sphinx.application.Sphinx.emit_firstresult", emit_firstresult_patch):
        builder("pyskipexample")

    options = ["members", "undoc-members", "special-members"]

    mock_calls = [
        call(
            "autoapi-skip-member",
            "module",
            "example",
            _CompareInstanceType(PythonModule),
            False,
            options,
        ),
        call(
            "autoapi-skip-member",
            "function",
            "example.foo",
            _CompareInstanceType(PythonFunction),
            False,
            options,
        ),
        call(
            "autoapi-skip-member",
            "class",
            "example.Bar",
            _CompareInstanceType(PythonClass),
            False,
            options,
        ),
        call(
            "autoapi-skip-member",
            "class",
            "example.Baf",
            _CompareInstanceType(PythonClass),
            False,
            options,
        ),
        call(
            "autoapi-skip-member",
            "data",
            "example.baz",
            _CompareInstanceType(PythonData),
            False,
            options,
        ),
        call(
            "autoapi-skip-member",
            "data",
            "example.anchor",
            _CompareInstanceType(PythonData),
            False,
            options,
        ),
        call(
            "autoapi-skip-member",
            "method",
            "example.Bar.m",
            _CompareInstanceType(PythonMethod),
            False,
            options,
        ),
        call(
            "autoapi-skip-member",
            "method",
            "example.Baf.m",
            _CompareInstanceType(PythonMethod),
            False,
            options,
        ),
    ]
    for mock_call in mock_calls:
        assert mock_call in emit_firstresult_patch.mock_calls


class TestComplexPackage:
    @pytest.fixture(autouse=True, scope="class")
    def built(self, builder):
        builder("pypackagecomplex")

    def test_public_chain_resolves(self):
        submodule_path = "_build/text/autoapi/complex/subpackage/submodule/index.txt"
        with io.open(submodule_path, encoding="utf8") as submodule_handle:
            submodule_file = submodule_handle.read()

        assert "Part of a public resolution chain." in submodule_file

        subpackage_path = "_build/text/autoapi/complex/subpackage/index.txt"
        with io.open(subpackage_path, encoding="utf8") as subpackage_handle:
            subpackage_file = subpackage_handle.read()

        assert "Part of a public resolution chain." in subpackage_file

        package_path = "_build/text/autoapi/complex/index.txt"
        with io.open(package_path, encoding="utf8") as package_handle:
            package_file = package_handle.read()

        assert "Part of a public resolution chain." in package_file

    def test_private_made_public(self):
        submodule_path = "_build/text/autoapi/complex/subpackage/submodule/index.txt"
        with io.open(submodule_path, encoding="utf8") as submodule_handle:
            submodule_file = submodule_handle.read()

        assert "A private function made public by import." in submodule_file

    def test_multiple_import_locations(self):
        submodule_path = "_build/text/autoapi/complex/subpackage/submodule/index.txt"
        with io.open(submodule_path, encoding="utf8") as submodule_handle:
            submodule_file = submodule_handle.read()

        assert "A public function imported in multiple places." in submodule_file

        subpackage_path = "_build/text/autoapi/complex/subpackage/index.txt"
        with io.open(subpackage_path, encoding="utf8") as subpackage_handle:
            subpackage_file = subpackage_handle.read()

        assert "A public function imported in multiple places." in subpackage_file

        package_path = "_build/text/autoapi/complex/index.txt"
        with io.open(package_path, encoding="utf8") as package_handle:
            package_file = package_handle.read()

        assert "A public function imported in multiple places." in package_file

    def test_simple_wildcard_imports(self):
        wildcard_path = "_build/text/autoapi/complex/wildcard/index.txt"
        with io.open(wildcard_path, encoding="utf8") as wildcard_handle:
            wildcard_file = wildcard_handle.read()

        assert "public_chain" in wildcard_file
        assert "now_public_function" in wildcard_file
        assert "public_multiple_imports" in wildcard_file
        assert "module_level_method" in wildcard_file

    def test_wildcard_chain(self):
        wildcard_path = "_build/text/autoapi/complex/wildchain/index.txt"
        with io.open(wildcard_path, encoding="utf8") as wildcard_handle:
            wildcard_file = wildcard_handle.read()

        assert "public_chain" in wildcard_file
        assert "module_level_method" in wildcard_file

    def test_wildcard_all_imports(self):
        wildcard_path = "_build/text/autoapi/complex/wildall/index.txt"
        with io.open(wildcard_path, encoding="utf8") as wildcard_handle:
            wildcard_file = wildcard_handle.read()

        assert "not_all" not in wildcard_file
        assert "NotAllClass" not in wildcard_file
        assert "does_not_exist" not in wildcard_file
        assert "SimpleClass" in wildcard_file
        assert "simple_function" in wildcard_file
        assert "public_chain" in wildcard_file
        assert "module_level_method" in wildcard_file

    def test_no_imports_in_module_with_all(self):
        foo_path = "_build/text/autoapi/complex/foo/index.txt"
        with io.open(foo_path, encoding="utf8") as foo_handle:
            foo_file = foo_handle.read()

        assert "module_level_method" not in foo_file

    def test_all_overrides_import_in_module_with_all(self):
        foo_path = "_build/text/autoapi/complex/foo/index.txt"
        with io.open(foo_path, encoding="utf8") as foo_handle:
            foo_file = foo_handle.read()

        assert "PublicClass" in foo_file

    def test_parses_unicode_file(self):
        foo_path = "_build/text/autoapi/complex/unicode_data/index.txt"
        with io.open(foo_path, encoding="utf8") as foo_handle:
            foo_file = foo_handle.read()

        assert "unicode_str" in foo_file


class TestComplexPackageParallel:
    @pytest.fixture(autouse=True, scope="class")
    def built(self, builder):
        builder("pypackagecomplex", parallel=2)

    def test_success(self):
        pass


def test_caching(builder):
    mtimes = (0, 0)

    def record_mtime():
        nonlocal mtimes
        mtime = 0
        for root, _, files in os.walk("_build/text/autoapi"):
            for name in files:
                this_mtime = os.path.getmtime(os.path.join(root, name))
                mtime = max(mtime, this_mtime)

        mtimes = (*mtimes[1:], mtime)

    builder("pypackagecomplex", confoverrides={"autoapi_keep_files": True})
    record_mtime()

    rebuild(confoverrides={"autoapi_keep_files": True})
    record_mtime()

    assert mtimes[1] == mtimes[0]

    # Check that adding a file rebuilds the docs
    extra_file = "complex/new.py"
    with open(extra_file, "w") as out_f:
        out_f.write("\n")

    try:
        rebuild(confoverrides={"autoapi_keep_files": True})
    finally:
        os.remove(extra_file)

    record_mtime()
    assert mtimes[1] != mtimes[0]

    # Removing a file also rebuilds the docs
    rebuild(confoverrides={"autoapi_keep_files": True})
    record_mtime()
    assert mtimes[1] != mtimes[0]

    # Changing not keeping files always builds
    rebuild()
    record_mtime()
    assert mtimes[1] != mtimes[0]


class TestImplicitNamespacePackage:
    @pytest.fixture(autouse=True, scope="class")
    def built(self, builder):
        builder("py3implicitnamespace")

    def test_sibling_import_from_namespace(self):
        example_path = "_build/text/autoapi/namespace/example/index.txt"
        with io.open(example_path, encoding="utf8") as example_handle:
            example_file = example_handle.read()

        assert "namespace.example.first_method" in example_file

    def test_sub_sibling_import_from_namespace(self):
        example_path = "_build/text/autoapi/namespace/example/index.txt"
        with io.open(example_path, encoding="utf8") as example_handle:
            example_file = example_handle.read()

        assert "namespace.example.second_sub_method" in example_file


def test_custom_jinja_filters(builder, tmp_path):
    py_templates = tmp_path / "python"
    py_templates.mkdir()
    orig_py_templates = pathlib.Path(autoapi.settings.TEMPLATE_DIR) / "python"
    orig_template = (orig_py_templates / "class.rst").read_text()
    (py_templates / "class.rst").write_text(
        orig_template.replace("obj.docstring", "obj.docstring|prepare_docstring")
    )

    confoverrides = {
        "autoapi_prepare_jinja_env": (
            lambda jinja_env: jinja_env.filters.update(
                {
                    "prepare_docstring": (
                        lambda docstring: "This is using custom filters."
                    )
                }
            )
        ),
        "autoapi_template_dir": str(tmp_path),
    }
    builder("pyexample", confoverrides=confoverrides)

    example_path = "_build/text/autoapi/example/index.txt"
    with io.open(example_path, encoding="utf8") as example_handle:
        example_file = example_handle.read()

    assert "This is using custom filters." in example_file


def test_string_module_attributes(builder):
    """Test toggle for multi-line string attribute values (GitHub #267)."""
    keep_rst = {
        "autoapi_keep_files": True,
        "autoapi_root": "_build/autoapi",  # Preserve RST files under _build for cleanup
    }
    builder("py3example", confoverrides=keep_rst)

    example_path = os.path.join(keep_rst["autoapi_root"], "example", "index.rst")
    with io.open(example_path, encoding="utf8") as example_handle:
        example_file = example_handle.read()

    code_snippet_contents = [
        ".. py:data:: code_snippet",
        "   :annotation: = Multiline-String",
        "",
        "    .. raw:: html",
        "",
        "        <details><summary>Show Value</summary>",
        "",
        "    .. code-block:: text",
        "        :linenos:",
        "",
        "        ",  # <--- Line array monstrosity to preserve these leading spaces
        "        # -*- coding: utf-8 -*-",
        "        from __future__ import absolute_import, division, print_function, unicode_literals",
        "        # from future.builtins.disabled import *",
        "        # from builtins import *",
        "",
        """        print("chunky o'block")""",
        "",
        "",
        "    .. raw:: html",
        "",
        "        </details>",
    ]
    assert "\n".join(code_snippet_contents) in example_file


class TestAutodocTypehintsPackage:
    """Test integrations with the autodoc.typehints extension."""

    @pytest.fixture(autouse=True, scope="class")
    def built(self, builder):
        builder("pyautodoc_typehints")

    def test_renders_typehint(self):
        example_path = "_build/text/autoapi/example/index.txt"
        with io.open(example_path, encoding="utf8") as example_handle:
            example_file = example_handle.read()

        assert "(*int*)" in example_file

    def test_renders_typehint_in_second_module(self):
        example2_path = "_build/text/autoapi/example2/index.txt"
        with io.open(example2_path, encoding="utf8") as example2_handle:
            example2_file = example2_handle.read()

        assert "(*int*)" in example2_file
