# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import io
import json
import os
from argparse import Namespace

import mozinfo
import pytest
from manifestparser import TestManifest, expression
from moztest.selftest.fixtures import binary_fixture, setup_test_harness  # noqa

here = os.path.abspath(os.path.dirname(__file__))
setup_args = [os.path.join(here, "files"), "mochitest", "testing/mochitest"]


@pytest.fixture
def create_manifest(tmpdir, build_obj):
    def inner(string, name="manifest.ini"):
        manifest = tmpdir.join(name)
        manifest.write(string, ensure=True)
        path = str(manifest)
        return TestManifest(manifests=(path,), strict=False, rootdir=tmpdir.strpath)

    return inner


@pytest.fixture(scope="function")
def parser(request):
    parser = pytest.importorskip("mochitest_options")

    app = getattr(request.module, "APP", "generic")
    return parser.MochitestArgumentParser(app=app)


@pytest.fixture(scope="function")
def runtests(setup_test_harness, binary, parser, request):
    """Creates an easy to use entry point into the mochitest harness.

    :returns: A function with the signature `*tests, **opts`. Each test is a file name
              (relative to the `files` dir). At least one is required. The opts are
              used to override the default mochitest options, they are optional.
    """
    flavor = "plain"
    if "flavor" in request.fixturenames:
        flavor = request.getfixturevalue("flavor")

    runFailures = ""
    if "runFailures" in request.fixturenames:
        runFailures = request.getfixturevalue("runFailures")

    restartAfterFailure = False
    if "restartAfterFailure" in request.fixturenames:
        restartAfterFailure = request.getfixturevalue("restartAfterFailure")

    setup_test_harness(*setup_args, flavor=flavor)

    runtests = pytest.importorskip("runtests")

    mochitest_root = runtests.SCRIPT_DIR
    if flavor == "plain":
        test_root = os.path.join(mochitest_root, "tests", "selftests")
        manifest_name = "mochitest.ini"
    elif flavor == "browser-chrome":
        test_root = os.path.join(mochitest_root, "browser", "tests", "selftests")
        manifest_name = "browser.ini"
    else:
        raise Exception(f"Invalid flavor {flavor}!")

    buf = io.StringIO()
    options = vars(parser.parse_args([]))
    options.update(
        {
            "app": binary,
            "flavor": flavor,
            "runFailures": runFailures,
            "restartAfterFailure": restartAfterFailure,
            "keep_open": False,
            "log_raw": [buf],
        }
    )

    if runFailures == "selftest":
        options["crashAsPass"] = True
        options["timeoutAsPass"] = True
        runtests.mozinfo.update({"selftest": True})

    if not os.path.isdir(runtests.build_obj.bindir):
        package_root = os.path.dirname(mochitest_root)
        options.update(
            {
                "certPath": os.path.join(package_root, "certs"),
                "utilityPath": os.path.join(package_root, "bin"),
            }
        )
        options["extraProfileFiles"].append(
            os.path.join(package_root, "bin", "plugins")
        )

    options.update(getattr(request.module, "OPTIONS", {}))

    def normalize(test):
        if isinstance(test, str):
            test = [test]
        return [
            {
                "name": t,
                "relpath": t,
                "path": os.path.join(test_root, t),
                # add a dummy manifest file because mochitest expects it
                "manifest": os.path.join(test_root, manifest_name),
                "manifest_relpath": manifest_name,
                "skip-if": runFailures,
            }
            for t in test
        ]

    def inner(*tests, **opts):
        assert len(tests) > 0

        # Inject a TestManifest in the runtests option if one
        # has not been already included by the caller.
        if not isinstance(options["manifestFile"], TestManifest):
            manifest = TestManifest()
            options["manifestFile"] = manifest
            # pylint --py3k: W1636
            manifest.tests.extend(list(map(normalize, tests))[0])
            options.update(opts)

        result = runtests.run_test_harness(parser, Namespace(**options))
        out = json.loads("[" + ",".join(buf.getvalue().splitlines()) + "]")
        buf.close()
        return result, out

    return inner


@pytest.fixture
def build_obj(setup_test_harness):
    setup_test_harness(*setup_args)
    mochitest_options = pytest.importorskip("mochitest_options")
    return mochitest_options.build_obj


@pytest.fixture(autouse=True)
def skip_using_mozinfo(request, setup_test_harness):
    """Gives tests the ability to skip based on values from mozinfo.

    Example:
        @pytest.mark.skip_mozinfo("!e10s || os == 'linux'")
        def test_foo():
            pass
    """

    setup_test_harness(*setup_args)
    runtests = pytest.importorskip("runtests")
    runtests.update_mozinfo()

    skip_mozinfo = request.node.get_closest_marker("skip_mozinfo")
    if skip_mozinfo:
        value = skip_mozinfo.args[0]
        if expression.parse(value, **mozinfo.info):
            pytest.skip(f"skipped due to mozinfo match: \n{value}")
