# mypy: disable-error-code="no-untyped-def, no-untyped-call"
from __future__ import annotations
import tempfile
import pytest
from pyproject2conda import parser
from pyproject2conda.utils import get_in

from textwrap import dedent


def test_get_in():
    d = {"a": {"b": {"c": 3}}}

    assert get_in(["a", "b", "c"], d) == 3

    assert get_in(["a", "d"], d, "hello") == "hello"


def test_list_to_string():
    assert parser._list_to_str(["a", "b"], eol=True) == "a\nb\n"
    assert parser._list_to_str(["a", "b"], eol=False) == "a\nb"
    assert parser._list_to_str(None) == ""


def test_match_p2c_comment():
    expected = "-c -d"
    for comment in [
        "#p2c: -c -d",
        "# p2c: -c -d",
        "  #p2c: -c -d",
        "# p2c: -c -d # some other thing",
        "# some other thing # p2c: -c -d # another thing",
    ]:
        match = parser._match_p2c_comment(comment)

        assert match == expected

    # check for skip
    for comment in [
        "##p2c: -c -d",
        "## p2c: -c -d",
        "  ##p2c: -c -d",
        "## p2c: -c -d # some other thing",
        "# some other thing ## p2c: -c -d # another thing",
    ]:
        match = parser._match_p2c_comment(comment)

        assert match is None


def test_parse_p2c():
    def get_expected(pip=False, skip=False, channel=None, package=None):
        if package is None:
            package = []
        return {
            "pip": pip,
            "skip": skip,
            "channel": channel,
            "package": package,
        }

    assert parser._parse_p2c(None) is None

    assert parser._parse_p2c("--pip") == get_expected(pip=True)
    assert parser._parse_p2c("-p") == get_expected(pip=True)

    assert parser._parse_p2c("--skip") == get_expected(skip=True)
    assert parser._parse_p2c("-s") == get_expected(skip=True)

    assert parser._parse_p2c("-s -c conda-forge") == get_expected(
        skip=True, channel="conda-forge"
    )

    assert parser._parse_p2c("athing>=0.3,<0.2 ") == get_expected(
        package=["athing>=0.3,<0.2"]
    )

    assert parser._parse_p2c("athing>=0.3,<0.2 bthing ") == get_expected(
        package=["athing>=0.3,<0.2", "bthing"]
    )

    assert parser._parse_p2c("'athing >= 0.3, <0.2' bthing ") == get_expected(
        package=["athing >= 0.3, <0.2", "bthing"]
    )


def test_value_comment_pairs():
    d: list[tuple[str | None, str | None]] = [(None, "# p2c: --pip")]

    with pytest.raises(ValueError):
        out = parser.value_comment_pairs_to_conda(d)

    d = [("hello", "# p2c: there")]

    out = parser.value_comment_pairs_to_conda(d)

    assert out["dependencies"] == ["hello", "there"]


def test_header():
    expected = dedent(
        """\
#
# This file is autogenerated by pyproject2conda.
# You should not manually edit this file.
# Instead edit the corresponding pyproject.toml file.
#"""
    )

    assert expected == parser._create_header()

    cmd = "hello"
    out = parser._create_header(cmd=cmd)

    header = dedent(
        f"""\
#
# This file is autogenerated by pyproject2conda
# with the following command:
#
#     $ {cmd}
#
# You should not manually edit this file.
# Instead edit the corresponding pyproject.toml file.
#"""
    )

    assert out == header


def test_yaml_to_str():
    d = {"dep": ["a", "b"]}

    s = parser._yaml_to_string(d, add_final_eol=False)

    expected = """\
    dep:
      - a
      - b"""

    assert s == dedent(expected)

    s = parser._yaml_to_string(d, add_final_eol=True)

    expected = """\
    dep:
      - a
      - b
    """

    assert s == dedent(expected)


def test_optional_write():
    from pathlib import Path

    s = "hello"
    with tempfile.TemporaryDirectory() as d:
        p = Path(d) / "tmp.txt"

        with open(p, "w") as f:
            parser._optional_write(s, f)

        with open(p, "r") as f:
            test = f.read()

        assert test == s


def test_output_to_yaml():
    s = parser._output_to_yaml(
        dependencies=["a"],
        channels=["conda-forge"],
        pip=["pip-thing"],
        name="hello",
    )

    expected = """\
name: hello
channels:
  - conda-forge
dependencies:
  - a
  - pip
  - pip:
      - pip-thing
    """

    assert s == dedent(expected)


def test_infer():
    toml = dedent(
        """\
    [project]
    name = "hello"
    dependencies = [
    "athing", # p2c: -p # a comment
    "bthing", # p2c: -s bthing-conda
    "cthing; python_version<'3.10'", # p2c: -c conda-forge
    ]

    [project.optional-dependencies]
    test = [
    "pandas",
    "pytest", # p2c: -c conda-forge
    ]
    dev-extras = [
    # p2c: -s additional-thing # this is an additional conda package
    "matplotlib", # p2c: -s conda-matplotlib
    ]
    dev = [
    "hello[test]",
    "hello[dev-extras]",
    ]
    dist-pypi = [
    "setuptools",
    "build", # p2c: -p
    ]


    [tool.pyproject2conda]
    channels = ['conda-forge']
    """
    )

    d = parser.PyProject2Conda.from_string(toml)
    with pytest.raises(ValueError):
        d.to_conda_yaml(python_include="infer")


def test_package_name():
    toml = dedent(
        """\
    [project]
    requires-python = ">=3.8,<3.11"
    dependencies = [
    "athing", # p2c: -p # a comment
    "bthing", # p2c: -s bthing-conda
    "cthing; python_version<'3.10'", # p2c: -c conda-forge
    ]

    [project.optional-dependencies]
    test = [
    "pandas",
    "pytest", # p2c: -c conda-forge
    ]
    dev-extras = [
    # p2c: -s additional-thing # this is an additional conda package
    "matplotlib", # p2c: -s conda-matplotlib
    ]
    dev = [
    "hello[test]",
    "hello[dev-extras]",
    ]
    dist-pypi = [
    "setuptools",
    "build", # p2c: -p
    ]


    [tool.pyproject2conda]
    channels = ['conda-forge']
    """
    )
    d = parser.PyProject2Conda.from_string(toml)
    with pytest.raises(ValueError):
        d.to_conda_lists()


def test_complete():
    toml = dedent(
        """\
    [project]
    name = "hello"
    requires-python = ">=3.8, <3.11"
    dependencies = [
    "athing", # p2c: -p # a comment
    "bthing", # p2c: -s bthing-conda
    "cthing; python_version<'3.10'", # p2c: -c conda-forge
    ]

    [project.optional-dependencies]
    test = [
    "pandas",
    "pytest", # p2c: -c conda-forge
    ]
    dev-extras = [
    # p2c: -s additional-thing # this is an additional conda package
    "matplotlib", # p2c: -s conda-matplotlib
    ]
    dev = [
    "hello[test]",
    "hello[dev-extras]",
    ]
    dist-pypi = [
    "setuptools",
    "build", # p2c: -p
    ]


    [tool.pyproject2conda]
    channels = ['conda-forge']
    """
    )

    d = parser.PyProject2Conda.from_string(toml)

    # test unknown extra
    with pytest.raises(ValueError):
        d.to_conda_yaml(extras="a-thing")

    # test list:
    assert d.list_extras() == ["test", "dev-extras", "dev", "dist-pypi"]

    assert d._get_opts("hello", "there") == []

    expected = """\
channels:
  - conda-forge
dependencies:
  - bthing-conda
  - conda-forge::cthing
  - pip
  - pip:
      - athing
    """

    assert dedent(expected) == d.to_conda_yaml()

    # test -p option
    expected = """\
channels:
  - conda-forge
dependencies:
  - python>=3.8,<3.11
  - bthing-conda
  - conda-forge::cthing
  - pip
  - pip:
      - athing
    """
    assert dedent(expected) == d.to_conda_yaml(python_include="infer")

    # with remove spaces:
    expected = """\
channels:
  - conda-forge
dependencies:
  - python>=3.8, <3.11
  - bthing-conda
  - conda-forge::cthing
  - pip
  - pip:
      - athing
    """
    assert dedent(expected) == d.to_conda_yaml(
        python_include="infer", remove_whitespace=False
    )

    expected = """\
channels:
  - conda-forge
dependencies:
  - python=3.9
  - bthing-conda
  - conda-forge::cthing
  - pip
  - pip:
      - athing
    """
    assert dedent(expected) == d.to_conda_yaml(python_include="python=3.9")

    # test passing python_version
    expected = """\
channels:
  - conda-forge
dependencies:
  - python=3.10
  - bthing-conda
  - pip
  - pip:
      - athing
    """
    assert dedent(expected) == d.to_conda_yaml(
        python_include="python=3.10", python_version="3.10"
    )

    out = d.to_conda_yaml(channels="hello")

    expected = """\
channels:
  - hello
dependencies:
  - bthing-conda
  - conda-forge::cthing
  - pip
  - pip:
      - athing
    """

    assert dedent(expected) == out

    out = d.to_conda_yaml(extras="test", sort=False)

    expected = """\
channels:
  - conda-forge
dependencies:
  - bthing-conda
  - conda-forge::cthing
  - pandas
  - conda-forge::pytest
  - pip
  - pip:
      - athing
    """

    assert dedent(expected) == out

    out = d.to_conda_yaml(extras="test", sort=True)

    expected = """\
channels:
  - conda-forge
dependencies:
  - bthing-conda
  - conda-forge::cthing
  - conda-forge::pytest
  - pandas
  - pip
  - pip:
      - athing
    """

    assert dedent(expected) == out

    out = d.to_conda_yaml(extras="dist-pypi", include_base_dependencies=False)

    expected = """\
channels:
  - conda-forge
dependencies:
  - setuptools
  - pip
  - pip:
      - build
    """

    assert out == dedent(expected)

    expected = """\
channels:
  - conda-forge
dependencies:
  - bthing-conda
  - conda-forge::cthing
  - pandas
  - conda-forge::pytest
  - additional-thing
  - conda-matplotlib
  - pip
  - pip:
      - athing
    """

    assert dedent(expected) == d.to_conda_yaml("dev", sort=False)

    expected = """\
channels:
  - conda-forge
dependencies:
  - additional-thing
  - bthing-conda
  - conda-forge::cthing
  - conda-forge::pytest
  - conda-matplotlib
  - pandas
  - pip
  - pip:
      - athing
    """

    assert dedent(expected) == d.to_conda_yaml("dev")

    # Test deps/reqs
    expected = """\
channels:
  - conda-forge
dependencies:
  - bthing-conda
  - conda-forge::cthing
  - dep
  - pip
  - pip:
      - athing
      - req
    """

    assert dedent(expected) == d.to_conda_yaml(
        deps=["dep;python_version<'3.10'"], reqs=["req"]
    )

    expected = """\
channels:
  - conda-forge
dependencies:
  - python=3.10
  - bthing-conda
  - pip
  - pip:
      - athing
      - req;python_version<'3.10'
    """
    assert dedent(expected) == d.to_conda_yaml(
        deps=["dep;python_version<'3.10'"],
        reqs=["req;python_version<'3.10'"],
        python_include="python=3.10",
        python_version="3.10",
    )


def test_missing_dependencies():
    toml = dedent(
        """\
    [project]
    name = "hello"
    requires-python = ">=3.8,<3.11"
    dependencies = []

    [project.optional-dependencies]
    test = [
    "pandas",
    "pytest", # p2c: -c conda-forge
    "pytest-accept", # p2c: -p
    ]
    dev-extras = [
    # p2c: -s additional-thing # this is an additional conda package
    "matplotlib", # p2c: -s conda-matplotlib
    ]
    dev = [
    "hello[test]",
    "hello[dev-extras]",
    ]

    [tool.pyproject2conda]
    channels = ['conda-forge']
    """
    )

    d = parser.PyProject2Conda.from_string(toml)

    expected = """\
channels:
  - conda-forge
dependencies:
  - conda-forge::pytest
  - pandas
  - pip
  - pip:
      - pytest-accept
    """

    assert dedent(expected) == d.to_conda_yaml(
        extras="test",
        include_base_dependencies=False,
    )

    toml = dedent(
        """\
    [project]
    name = "hello"
    requires-python = ">=3.8,<3.11"

    [project.optional-dependencies]
    test = [
    "pandas",
    "pytest", # p2c: -c conda-forge
    "pytest-accept", # p2c: -p
    ]
    dev-extras = [
    # p2c: -s additional-thing # this is an additional conda package
    "matplotlib", # p2c: -s conda-matplotlib
    ]
    dev = [
    "hello[test]",
    "hello[dev-extras]",
    ]

    [tool.pyproject2conda]
    channels = ['conda-forge']
    """
    )

    d = parser.PyProject2Conda.from_string(toml)

    assert dedent(expected) == d.to_conda_yaml(
        extras="test",
        include_base_dependencies=False,
    )

    assert dedent(expected) == d.to_conda_yaml(
        extras="test",
        include_base_dependencies=True,
    )

    # check output has no dependencies:
    for attr in ["to_conda_yaml", "to_requirements"]:
        f = getattr(d, attr)
        with pytest.raises(ValueError):
            f()

        assert f(allow_empty=True) == "No dependencies for this environment\n"


def test_spaces():
    toml = dedent(
        """\
    [project]
    name = "hello"
    requires-python = ">=3.8, <3.11"
    dependencies = [
    "athing >0.5", # p2c: -p # a comment
    "bthing > 1.0", # p2c: -s 'bthing-conda > 2.0'
    "cthing; python_version < '3.10'", # p2c: -c conda-forge
    ]


    [tool.pyproject2conda]
    channels = ['conda-forge']
    """
    )

    d = parser.PyProject2Conda.from_string(toml)

    expected = """\
channels:
  - conda-forge
dependencies:
  - python>=3.8, <3.11
  - bthing-conda > 2.0
  - conda-forge::cthing
  - pip
  - pip:
      - athing >0.5
    """
    assert dedent(expected) == d.to_conda_yaml(
        python_include="infer", remove_whitespace=False
    )

    expected = """\
channels:
  - conda-forge
dependencies:
  - python>=3.8,<3.11
  - bthing-conda>2.0
  - conda-forge::cthing
  - pip
  - pip:
      - athing>0.5
    """
    assert dedent(expected) == d.to_conda_yaml(
        python_include="infer", remove_whitespace=True
    )

    expected = """\
    athing >0.5
    bthing > 1.0
    cthing; python_version < '3.10'
    """

    assert dedent(expected) == d.to_requirements(remove_whitespace=False)

    expected = """\
    athing>0.5
    bthing>1.0
    cthing;python_version<'3.10'
    """

    assert dedent(expected) == d.to_requirements(remove_whitespace=True)
