# Authors: The MNE-Python contributors.
# License: BSD-3-Clause
# Copyright the MNE-Python contributors.

import webbrowser

import pytest

from mne import grade_to_tris, open_docs
from mne.utils import (
    catch_logging,
    copy_doc,
    copy_function_doc_to_method_doc,
    deprecated,
    deprecated_alias,
    legacy,
    linkcode_resolve,
)


@pytest.mark.parametrize("obj", (grade_to_tris,))
def test_doc_filling(obj):
    """Test that docs are filled properly."""
    doc = obj.__doc__
    assert "verbose : " in doc


def test_deprecated_alias():
    """Test deprecated_alias."""

    def new_func():
        """Do something."""
        pass

    deprecated_alias("old_func", new_func)
    assert old_func  # noqa
    assert "has been deprecated in favor of new_func" in old_func.__doc__  # noqa
    assert "deprecated" not in new_func.__doc__


@deprecated("deprecated func")
def deprecated_func():
    """Do something."""
    pass


@legacy("replacement_func")
def legacy_func():
    """Do something."""
    pass


@deprecated("deprecated class")
class deprecated_class:
    def __init__(self):
        pass

    @deprecated("deprecated method")
    def bad(self):
        pass


@legacy("replacement_class")
class legacy_class:  # noqa D101
    def __init__(self):
        pass

    @legacy("replacement_method")
    def bad(self):  # noqa D102
        pass


@pytest.mark.parametrize(
    ("msg", "klass", "func"),
    (
        ("deprecated", deprecated_class, deprecated_func),
        ("legacy", legacy_class, legacy_func),
    ),
)
def test_deprecated_and_legacy(msg, func, klass):
    """Test deprecated and legacy decorators."""
    if msg == "deprecated":
        with pytest.warns(FutureWarning, match=f"{msg} class"):
            _klass = klass()
        with pytest.warns(FutureWarning, match=f"{msg} method"):
            _klass.bad()
        with pytest.warns(FutureWarning, match=f"{msg} func"):
            func()
    else:
        with catch_logging(verbose="info") as log:
            _klass = klass()
            _klass.bad()
            func()
        log = log.getvalue()
        for kind in ("class", "method", "func"):
            assert f"New code should use replacement_{kind}" in log
    assert msg.upper() in klass.__init__.__doc__
    assert msg.upper() in klass.bad.__doc__
    assert msg.upper() in _klass.bad.__doc__
    assert msg.upper() in func.__doc__


def test_copy_doc():
    """Test decorator for copying docstrings."""

    class A:
        def m1():
            """Docstring for m1."""
            pass

    class B:
        def m1():
            pass

    class C(A):
        @copy_doc(A.m1)
        def m1():
            pass

    assert C.m1.__doc__ == "Docstring for m1."
    pytest.raises(ValueError, copy_doc(B.m1), C.m1)


def test_copy_function_doc_to_method_doc():
    """Test decorator for reusing function docstring as method docstrings."""

    def f1(obj, a, b, c):
        """Docstring for f1.

        Parameters
        ----------
        obj : object
            Some object. This description also has

            blank lines in it.
        a : int
            Parameter a
        b : int
            Parameter b
        """
        pass

    def f2(obj):
        """Docstring for f2.

        Parameters
        ----------
        object : object
            Only one parameter

        Returns
        -------
        nothing.
        """
        pass

    def f3(obj):
        """Docstring for f3.

        Parameters
        ----------
        object : object
            Only one parameter
        """
        pass

    def f4(obj):
        """Docstring for f4."""
        pass

    def f5(obj):  # noqa: D410, D411, D414
        """Docstring for f5.

        Parameters
        ----------
        Returns
        -------
        nothing.
        """
        pass

    class A:
        @copy_function_doc_to_method_doc(f1)
        def method_f1(self, a, b, c):
            pass

        @copy_function_doc_to_method_doc(f2)
        def method_f2(self):
            "method_f3 own docstring"
            pass

        @copy_function_doc_to_method_doc(f3)
        def method_f3(self):
            pass

    assert (
        A.method_f1.__doc__
        == """\
Docstring for f1.

Parameters
----------
a : int
    Parameter a
b : int
    Parameter b"""
    )

    assert (
        A.method_f2.__doc__
        == """\
Docstring for f2.

Returns
-------
nothing.
method_f3 own docstring"""
    )

    assert A.method_f3.__doc__ == "Docstring for f3.\n\n"
    pytest.raises(ValueError, copy_function_doc_to_method_doc(f5), A.method_f1)


def myfun(x):
    """Check url."""
    assert "mne.tools" in x


def test_open_docs():
    """Test doc launching."""
    old_tab = webbrowser.open_new_tab
    try:
        # monkey patch temporarily to prevent tabs from actually spawning
        webbrowser.open_new_tab = myfun
        open_docs()
        open_docs("tutorials", "dev")
        open_docs("examples", "stable")
        pytest.raises(ValueError, open_docs, "foo")
        pytest.raises(ValueError, open_docs, "api", "foo")
    finally:
        webbrowser.open_new_tab = old_tab


def test_linkcode_resolve():
    """Test linkcode resolving."""
    ex = "#L"
    url = linkcode_resolve("py", dict(module="mne", fullname="Epochs"))
    assert "/mne/epochs.py" + ex in url
    url = linkcode_resolve("py", dict(module="mne", fullname="compute_covariance"))
    assert "/mne/cov.py" + ex in url
    url = linkcode_resolve(
        "py", dict(module="mne", fullname="convert_forward_solution")
    )
    assert "/mne/forward/forward.py" + ex in url
    url = linkcode_resolve(
        "py", dict(module="mne", fullname="datasets.sample.data_path")
    )
    assert "/mne/datasets/sample/sample.py" + ex in url
