import os
import pytest
from pathlib import Path
from bs4 import BeautifulSoup
import docutils
import sphinx

from sphinx_tabs.tabs import JS_FILES, CSS_FILES

pytest_plugins = "sphinx.testing.fixtures"


def pytest_configure(config):
    config.addinivalue_line(
        "markers", "noautobuild: mark test to prevent autouse fixtures from running"
    )


@pytest.fixture(scope="session")
def rootdir():
    """Pytest uses this to find test documents."""
    return Path(__file__).parent.absolute() / "roots"


@pytest.fixture(autouse=True)
def auto_build_and_check(
    app,
    status,
    warning,
    check_build_success,
    get_sphinx_app_doctree,
    regress_sphinx_app_output,
    request,
):
    """
    Build and check build success and output regressions.
    Currently all tests start with this.
    Disable using a `noautobuild` mark.
    """
    if "noautobuild" in request.keywords:
        return
    app.build()
    check_build_success(status, warning)
    get_sphinx_app_doctree(app, regress=True)
    regress_sphinx_app_output(app)


@pytest.fixture()
def manual_build_and_check(
    app,
    status,
    warning,
    check_build_success,
    get_sphinx_app_doctree,
    regress_sphinx_app_output,
):
    """
    For manually triggering app build and check.
    """
    app.build()
    check_build_success(status, warning)
    get_sphinx_app_doctree(app, regress=True)
    regress_sphinx_app_output(app)


@pytest.fixture
def check_build_success():
    """Check build is successful and there are no warnings."""

    def check(status, warning):
        assert "build succeeded" in status.getvalue()
        warnings = warning.getvalue().strip()
        assert warnings == ""

    return check


@pytest.fixture
def regress_sphinx_app_output(file_regression, get_sphinx_app_output):
    """
    Get sphinx HTML output and regress inner body (other sections are
    non-deterministic).
    """

    def read(app, buildername="html", filename="index.html", encoding="utf-8"):
        content = get_sphinx_app_output(app, buildername, filename, encoding)

        if buildername == "html":
            soup = BeautifulSoup(content, "html.parser")

            # Remove output from ``pygments``, so that test only compares HTML of surrounding tabs
            for div in soup.find_all("div", {"class": "highlight"}):
                div.decompose()

            if sphinx.version_info < (8, 1):
                body = soup.select("div.body")[0]
                body.append(soup.new_tag("div", **{"class": "clearer"}))

            doc_div = soup.findAll("div", {"class": "documentwrapper"})[0]
            doc = doc_div.prettify()

        else:
            doc = content
        file_regression.check(
            doc, extension="." + filename.split(".")[-1], encoding="utf8"
        )

    return read


@pytest.fixture
def get_sphinx_app_doctree(file_regression):
    """Get sphinx doctree and optionally regress."""

    def read(app, docname="index", resolve=False, regress=False, replace=None):
        if resolve:
            doctree = app.env.get_and_resolve_doctree(docname, app.builder)
            extension = ".resolved.xml"
        else:
            doctree = app.env.get_doctree(docname)
            extension = ".xml"

        # convert absolute filenames
        for node in doctree.findall(lambda n: "source" in n):
            node["source"] = Path(node["source"]).name

        if regress:
            text = doctree.pformat()  # type: str
            for find, rep in (replace or {}).items():
                text = text.replace(find, rep)
            if sphinx.version_info < (7, 1):
                text = text.replace(
                    '<document source="index.rst">',
                    "<document source=\"index.rst\" translation_progress=\"{'total': 0, 'translated': 0}\">",
                )
            if docutils.__version_info__ < (0, 22):
                text = text.replace('="False"', '="0"')
                text = text.replace('linenos="True"', 'linenos="1"')
            file_regression.check(text, extension=extension)

        return doctree

    return read


@pytest.fixture
def check_asset_links(get_sphinx_app_output):
    """
    Check if all stylesheets and scripts (.js) have been referenced in HTML.
    Specify whether checking if assets are ``present`` or not ``present``.
    """

    def check(
        app,
        buildername="html",
        filename="index.html",
        encoding="utf-8",
        cssPresent=True,
        jsPresent=True,
    ):
        content = get_sphinx_app_output(app, buildername, filename, encoding)

        soup = BeautifulSoup(content, "html.parser")
        stylesheets = soup.find_all("link", {"rel": "stylesheet"}, href=True)
        css_refs = [s["href"] for s in stylesheets]

        scripts = soup.find_all("script", src=True)
        js_refs = [s["src"] for s in scripts]

        all_refs = css_refs + js_refs

        if cssPresent:
            css_present = all(any(a in ref for ref in all_refs) for a in CSS_FILES)
            assert css_present
        else:
            assert not "sphinx_tabs" in css_refs
        if jsPresent:
            js_present = all(any(a in ref for ref in js_refs) for a in JS_FILES)
            assert js_present
        else:
            assert not "sphinx_tabs" in js_refs

    return check


@pytest.fixture()
def get_sphinx_app_output():
    """Get content of a sphinx app output file."""

    def get(app, buildername="html", filename="index.html", encoding="utf-8"):
        outpath = Path(app.srcdir) / "_build" / buildername / filename
        if not outpath.exists():
            raise IOError("No output file exists: {}".format(outpath.as_posix()))

        return outpath.read_text(encoding=encoding)

    return get
