import os.path
import tempfile

import pytest

from galaxy.tool_util.deps import DependencyManager
from galaxy.tool_util.deps.conda_util import (
    best_search_result,
    CondaContext,
    CondaTarget,
    install_conda,
    installed_conda_targets,
)
from galaxy.tool_util.deps.requirements import (
    ToolRequirement,
    ToolRequirements,
)
from galaxy.tool_util.deps.resolvers import NullDependency
from galaxy.tool_util.deps.resolvers.conda import (
    CondaDependency,
    CondaDependencyResolver,
    DEFAULT_ENSURE_CHANNELS,
    MergedCondaDependency,
)
from .util import external_dependency_management


@external_dependency_management
def test_install_conda_build(tmp_path) -> None:
    conda_prefix = os.path.join(tmp_path, "miniconda3")
    cc = CondaContext(conda_prefix=conda_prefix, ensure_channels=DEFAULT_ENSURE_CHANNELS)
    assert not cc.is_conda_installed()
    assert install_conda(cc, force_conda_build=True) == 0
    assert cc.is_conda_installed()
    assert cc.conda_build_available


@pytest.fixture(scope="module")
def resolver(tmp_path_factory):
    dependency_manager = DependencyManager(tmp_path_factory.mktemp("dependencies"))
    return CondaDependencyResolver(
        dependency_manager,
        auto_init=True,
        auto_install=True,
        use_path_exec=False,  # For the test ensure this is always a clean install
    )


@external_dependency_management
def test_single_package_resolution(resolver: CondaDependencyResolver) -> None:
    # Test that a read-only resolver returns a NullDependency
    resolver.read_only = True
    package_name = "eXpress"
    req = ToolRequirement(name=package_name, version=None, type="package")
    dependency = resolver.resolve(req)
    assert isinstance(dependency, NullDependency)
    assert dependency.shell_commands() is None

    # Test that a (fake) environment generated from a non-lowercase package name
    # is still accepted by the resolver
    env_path = resolver.conda_context.env_path(f"__{package_name}@_uv_")
    os.mkdir(env_path)
    dependency = resolver.resolve(req)
    assert isinstance(dependency, CondaDependency)
    assert dependency.name == package_name
    assert dependency.version is None
    assert dependency.exact
    assert dependency.environment_path == env_path
    os.rmdir(env_path)

    # Test that a non-read-only resolver creates a conda environment (with a
    # lowercase package name) and returns a CondaDependency
    resolver.read_only = False
    dependency = resolver.resolve(req)
    assert isinstance(dependency, CondaDependency)
    assert dependency.name == package_name
    assert dependency.version is None
    assert dependency.exact
    assert dependency.environment_path == resolver.conda_context.env_path(f"__{package_name.lower()}@_uv_")
    assert dependency.shell_commands() is not None


@external_dependency_management
def test_multi_package_resolution(resolver: CondaDependencyResolver) -> None:
    # Test that a read-only resolver returns an empty list
    resolver.read_only = True
    reqs = ToolRequirements(
        [
            ToolRequirement(name="samtools", version=None, type="package"),
            ToolRequirement(name="eXpress", version=None, type="package"),
        ]
    )
    dependency_list = resolver.resolve_all(reqs)
    assert dependency_list == []

    # Test that a (fake) mulled environment generated from non-lowercase package
    # names is still accepted by the resolver
    env_path = resolver.conda_context.env_path(
        "mulled-v1-37955d8b3667efa38f1fe42bc37b4c9a03e67a90f0055d1db9d388b2f98fe3a1"
    )
    os.mkdir(env_path)
    dependency_list = resolver.resolve_all(reqs)
    assert len(dependency_list) == 2
    for dependency in dependency_list:
        assert isinstance(dependency, MergedCondaDependency)
        assert dependency.version is None
        assert dependency.exact
        assert dependency.environment_path == env_path
    os.rmdir(env_path)

    # Test that a non-read-only resolver creates a mulled conda environment
    # (with a hash from lowercase package names) and returns a list of
    # MergedCondaDependency
    resolver.read_only = False
    dependency_list = resolver.resolve_all(reqs)
    assert len(dependency_list) == 2
    for dependency in dependency_list:
        assert isinstance(dependency, MergedCondaDependency)
        assert dependency.version is None
        assert dependency.exact
        assert dependency.environment_path == resolver.conda_context.env_path(
            "mulled-v1-f36491d362c22cdb24f7de3e1ff978a5ee02cadc8c12a12ae06bce57bbf0765c"
        )


@external_dependency_management
def test_against_conda_prefix_regression() -> None:
    """Test that would fail if https://github.com/rtfd/readthedocs.org/issues/1902 regressed."""
    with tempfile.TemporaryDirectory(prefix="x" * 80) as base_path:  # a ridiculously long prefix
        job_dir = os.path.join(base_path, "000")
        dependency_manager = DependencyManager(base_path)
        resolver = CondaDependencyResolver(
            dependency_manager,
            auto_init=True,
            auto_install=True,
            use_path_exec=False,  # For the test ensure this is always a clean install
        )
        conda_context = resolver.conda_context
        assert len(list(installed_conda_targets(conda_context))) == 0
        req = ToolRequirement(name="samtools", version="0.1.16", type="package")
        dependency = resolver.resolve(req, job_directory=job_dir)
        assert dependency.shell_commands() is not None  # install should not fail anymore
        installed_targets = list(installed_conda_targets(conda_context))
        assert len(installed_targets) > 0


@external_dependency_management
def test_best_search_result(tmp_path) -> None:
    conda_context = CondaContext(
        conda_prefix=os.path.join(tmp_path, "_conda"),
        ensure_channels=DEFAULT_ENSURE_CHANNELS,
        condarc_override=os.path.join(tmp_path, "_condarc"),
    )
    install_conda(conda_context)
    hit, exact = best_search_result(CondaTarget("samtools"), conda_context)
    assert hit is not None
    assert hit["name"] == "samtools"
    assert exact is True
    hit, exact = best_search_result(CondaTarget("samtools", version="1.3.1", build="h0cf4675_11"), conda_context)
    assert hit is not None
    assert hit["name"] == "samtools"
    assert hit["version"] == "1.3.1"
    assert hit["build"] == "h0cf4675_11"
    assert exact is True
    # Search non-existent version
    hit, exact = best_search_result(CondaTarget("samtools", version="1.16"), conda_context)
    assert hit is not None
    assert hit["name"] == "samtools"
    assert exact is False
