import io
import json
import os
import sys
import shutil
from contextlib import contextmanager

from unittest.mock import patch
import pytest

import sphinx
from sphinx.application import Sphinx
from sphinx.errors import ExtensionError


@contextmanager
def sphinx_build(test_dir, confoverrides=None):
    os.chdir("tests/{0}".format(test_dir))
    try:
        app = Sphinx(
            srcdir=".",
            confdir=".",
            outdir="_build/text",
            doctreedir="_build/.doctrees",
            buildername="text",
            confoverrides=confoverrides,
        )
        app.build(force_all=True)
        yield
    finally:
        if os.path.exists("_build"):
            shutil.rmtree("_build")
        os.chdir("../..")


class LanguageIntegrationTests:
    def _run_test(self, test_dir, test_file, test_string):
        with sphinx_build(test_dir):
            with io.open(test_file, encoding="utf8") as fin:
                text = fin.read().strip()
                assert test_string in text


class TestJavaScript(LanguageIntegrationTests):
    def _js_read(self, path):
        return json.load(open("../fixtures/javascript.json"))

    @patch("autoapi.mappers.javascript.JavaScriptSphinxMapper.read_file", _js_read)
    def test_integration(self):
        self._run_test(
            "jsexample",
            "_build/text/autoapi/Circle/index.txt",
            "Creates an instance of Circle",
        )


@pytest.mark.skipif(
    sphinx.version_info >= (3,),
    reason="golangdomain extension does not support sphinx >=3",
)
class TestGo(LanguageIntegrationTests):
    def _go_read(self, path, **kwargs):
        return json.load(open("../fixtures/go.json"))

    @patch("autoapi.mappers.go.GoSphinxMapper.read_file", _go_read)
    def test_integration(self):
        self._run_test(
            "goexample",
            "_build/text/autoapi/main/index.txt",
            "CopyFuncs produces a json-annotated array of Func objects",
        )


@pytest.mark.skipif(
    sphinx.version_info >= (3,),
    reason="dotnetdomain extension does not support sphinx >=3",
)
class TestDotNet(LanguageIntegrationTests):
    def _dotnet_read(self, path):
        return json.load(open("../fixtures/dotnet.json"))

    # Mock this because it's slow otherwise
    def _dotnet_load(self, patterns, dirs, ignore=()):
        data = self.read_file(path="inmem")
        self.paths["inmem"] = data

    @staticmethod
    def _dotnet_finished(app, exception):
        pass

    @patch("autoapi.mappers.dotnet.DotNetSphinxMapper.load", _dotnet_load)
    @patch("autoapi.mappers.dotnet.DotNetSphinxMapper.read_file", _dotnet_read)
    @patch("autoapi.mappers.dotnet.DotNetSphinxMapper.build_finished", _dotnet_finished)
    def test_integration(self):
        self._run_test(
            "dotnetexample",
            "_build/text/autoapi/Microsoft/AspNet/Identity/IUserStore-TUser/index.txt",
            "Provides an abstraction for a store which manages user accounts.",
        )


class TestIntegration(LanguageIntegrationTests):
    def test_template_overrides(self):
        self._run_test(
            "templateexample",
            "_build/text/autoapi/example/index.txt",
            "This is a function template override",
        )


class TestTOCTree(LanguageIntegrationTests):
    def test_toctree_overrides(self):
        self._run_test("toctreeexample", "_build/text/index.txt", "API Reference")

    def test_toctree_domain_insertion(self):
        """
        Test that the example_function gets added to the TOC Tree
        """
        self._run_test("toctreeexample", "_build/text/index.txt", "* example_function")


class TestExtensionErrors:
    @pytest.fixture(autouse=True)
    def unload_go_and_dotnet_libraries(self):
        # unload dotnet and golang domain libraries, because they may be imported before
        for mod_name in ("sphinxcontrib.dotnetdomain", "sphinxcontrib.golangdomain"):
            try:
                del sys.modules[mod_name]
            except KeyError:
                pass

    @pytest.mark.parametrize(
        "proj_name, override_conf, err_msg",
        [
            (
                "toctreeexample",
                {"autoapi_type": "INVALID VALUE"},
                (
                    "Invalid autoapi_type setting, following values is "
                    'allowed: "dotnet", "go", "javascript", "python"'
                ),
            ),
            (
                "goexample",
                {"autoapi_type": "go", "extensions": ["autoapi.extension"]},
                (
                    "AutoAPI of type `go` requires following "
                    "packages to be installed and included in extensions list: "
                    "sphinxcontrib.golangdomain (available as "
                    '"sphinxcontrib-golangdomain" on PyPI)'
                ),
            ),
            (
                "dotnetexample",
                {"autoapi_type": "dotnet", "extensions": ["autoapi.extension"]},
                (
                    "AutoAPI of type `dotnet` requires following "
                    "packages to be installed and included in extensions list: "
                    "sphinxcontrib.dotnetdomain (available as "
                    '"sphinxcontrib-dotnetdomain" on PyPI)'
                ),
            ),
        ],
    )
    def test_extension_setup_errors(self, proj_name, override_conf, err_msg):
        with pytest.raises(ExtensionError) as err_info:
            with sphinx_build(proj_name, override_conf):
                pass

        assert str(err_info.value) == err_msg
