import importlib.machinery
import os
import posixpath
from pathlib import Path
from unittest.mock import patch

from isort import sections, settings
from isort.deprecated import finders
from isort.deprecated.finders import FindersManager
from isort.settings import Config


class TestFindersManager:
    def test_init(self):
        assert FindersManager(settings.DEFAULT_CONFIG)

        class ExceptionOnInit(finders.BaseFinder):
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                raise ValueError("test")

        with patch(
            "isort.deprecated.finders.FindersManager._default_finders_classes",
            FindersManager._default_finders_classes + (ExceptionOnInit,),  # type: ignore
        ):
            assert FindersManager(settings.Config(verbose=True))

    def test_no_finders(self):
        assert FindersManager(settings.DEFAULT_CONFIG, []).find("isort") is None

    def test_find_broken_finder(self):
        class ExceptionOnFind(finders.BaseFinder):
            def find(*args, **kwargs):
                raise ValueError("test")

        assert (
            FindersManager(settings.Config(verbose=True), [ExceptionOnFind]).find("isort") is None
        )


class AbstractTestFinder:
    kind = finders.BaseFinder

    @classmethod
    def setup_class(cls):
        cls.instance = cls.kind(settings.DEFAULT_CONFIG)  # type: ignore

    def test_create(self):
        assert self.kind(settings.DEFAULT_CONFIG)  # type: ignore

    def test_find(self):
        self.instance.find("isort")  # type: ignore
        self.instance.find("")  # type: ignore


class TestForcedSeparateFinder(AbstractTestFinder):
    kind = finders.ForcedSeparateFinder


class TestDefaultFinder(AbstractTestFinder):
    kind = finders.DefaultFinder


class TestKnownPatternFinder(AbstractTestFinder):
    kind = finders.KnownPatternFinder


class TestLocalFinder(AbstractTestFinder):
    kind = finders.LocalFinder


class TestPathFinder(AbstractTestFinder):
    kind = finders.PathFinder

    def test_conda_and_virtual_env(self, tmpdir):
        python3lib = tmpdir.mkdir("lib").mkdir("python3")
        python3lib.mkdir("site-packages").mkdir("y")
        python3lib.mkdir("n").mkdir("site-packages").mkdir("x")
        tmpdir.mkdir("z").join("__init__.py").write("__version__ = '1.0.0'")
        tmpdir.chdir()

        conda = self.kind(settings.Config(conda_env=str(tmpdir)), str(tmpdir))
        venv = self.kind(settings.Config(virtual_env=str(tmpdir)), str(tmpdir))
        assert conda.find("y") == venv.find("y") == "THIRDPARTY"
        assert conda.find("x") == venv.find("x") == "THIRDPARTY"
        assert conda.find("z") == "THIRDPARTY"
        assert conda.find("os") == venv.find("os") == "STDLIB"

    def test_default_section(self, tmpdir):
        tmpdir.join("file.py").write("import b\nimport a\n")
        assert self.kind(settings.Config(default_section="CUSTOM"), tmpdir).find("file") == "CUSTOM"

    def test_src_paths(self, tmpdir):
        tmpdir.join("file.py").write("import b\nimport a\n")
        assert (
            self.kind(settings.Config(src_paths=[Path(str(tmpdir))]), tmpdir).find("file")
            == settings.DEFAULT_CONFIG.default_section
        )


class TestRequirementsFinder(AbstractTestFinder):
    kind = finders.RequirementsFinder

    def test_no_pipreqs(self):
        with patch("isort.deprecated.finders.pipreqs", None):
            assert not self.kind(settings.DEFAULT_CONFIG).find("isort")

    def test_not_enabled(self):
        test_finder = self.kind(settings.DEFAULT_CONFIG)
        test_finder.enabled = False
        assert not test_finder.find("isort")

    def test_requirements_dir(self, tmpdir):
        tmpdir.mkdir("requirements").join("development.txt").write("x==1.00")
        test_finder = self.kind(settings.DEFAULT_CONFIG, str(tmpdir))
        assert test_finder.find("x")


def test_requirements_finder(tmpdir) -> None:
    subdir = tmpdir.mkdir("subdir").join("lol.txt")
    subdir.write("flask")
    req_file = tmpdir.join("requirements.txt")
    req_file.write("Django==1.11\n-e git+https://github.com/orsinium/deal.git#egg=deal\n")
    for path in (str(tmpdir), str(subdir)):
        finder = finders.RequirementsFinder(config=Config(), path=path)

        files = list(finder._get_files())
        assert len(files) == 1  # file finding
        assert files[0].endswith("requirements.txt")  # file finding
        assert set(finder._get_names(str(req_file))) == {"Django", "deal"}  # file parsing

        assert finder.find("django") == sections.THIRDPARTY  # package in reqs
        assert finder.find("flask") is None  # package not in reqs
        assert finder.find("deal") == sections.THIRDPARTY  # vcs

        assert len(finder.mapping) > 100  # type: ignore
        assert finder._normalize_name("deal") == "deal"
        assert finder._normalize_name("Django") == "django"  # lowercase
        assert finder._normalize_name("django_haystack") == "haystack"  # mapping
        assert finder._normalize_name("Flask-RESTful") == "flask_restful"  # convert `-`to `_`

    req_file.remove()


def test_path_finder(monkeypatch) -> None:
    config = config = Config()
    finder = finders.PathFinder(config=config)
    third_party_prefix = next(path for path in finder.paths if "site-packages" in path)
    ext_suffixes = importlib.machinery.EXTENSION_SUFFIXES
    imaginary_paths = {
        posixpath.join(finder.stdlib_lib_prefix, "example_1.py"),
        posixpath.join(third_party_prefix, "example_2.py"),
        posixpath.join(os.getcwd(), "example_3.py"),
    }
    imaginary_paths.update(
        {
            posixpath.join(third_party_prefix, "example_" + str(i) + ext_suffix)
            for i, ext_suffix in enumerate(ext_suffixes, 4)
        }
    )

    monkeypatch.setattr(
        "isort.deprecated.finders.exists_case_sensitive", lambda p: p in imaginary_paths
    )
    assert finder.find("example_1") == sections.STDLIB
    assert finder.find("example_2") == sections.THIRDPARTY
    assert finder.find("example_3") == settings.DEFAULT_CONFIG.default_section
    for i, _ in enumerate(ext_suffixes, 4):
        assert finder.find("example_" + str(i)) == sections.THIRDPARTY
