import os
import shutil
from os import environ
from pathlib import Path
from typing import List

import pytest
from modflow_devtools.misc import (
    get_model_paths,
    get_namefile_paths,
    get_packages,
    has_package,
    set_dir,
)


def test_set_dir(tmp_path):
    assert Path(os.getcwd()) != tmp_path
    with set_dir(tmp_path):
        assert Path(os.getcwd()) == tmp_path
    assert Path(os.getcwd()) != tmp_path


_repos_path = environ.get("REPOS_PATH")
if _repos_path is None:
    _repos_path = Path(__file__).parent.parent.parent.parent
_repos_path = Path(_repos_path).expanduser().absolute()
_testmodels_repo_path = _repos_path / "modflow6-testmodels"
_testmodels_repo_paths_mf6 = sorted(
    list((_testmodels_repo_path / "mf6").glob("test*"))
)
_testmodels_repo_paths_mf5to6 = sorted(
    list((_testmodels_repo_path / "mf5to6").glob("test*"))
)
_largetestmodels_repo_path = _repos_path / "modflow6-largetestmodels"
_largetestmodel_paths = sorted(list(_largetestmodels_repo_path.glob("test*")))
_examples_repo_path = _repos_path / "modflow6-examples"
_examples_path = _examples_repo_path / "examples"
_example_paths = (
    sorted(list(_examples_path.glob("ex-*")))
    if _examples_path.is_dir()
    else []
)


@pytest.mark.skipif(
    not any(_testmodels_repo_paths_mf6), reason="mf6 test models not found"
)
def test_get_packages():
    model_path = _testmodels_repo_paths_mf6[0]

    # simulation namefile
    namefile_path = model_path / "mfsim.nam"
    packages = get_packages(namefile_path)
    assert model_path.name == "test001a_Tharmonic"
    assert set(packages) >= {"tdis", "gwf", "ims", "npf", "chd", "oc", "dis"}

    # model namefile
    namefile_path = model_path / "flow15.nam"
    packages = get_packages(namefile_path)
    assert model_path.name == "test001a_Tharmonic"
    assert set(packages) >= {"npf", "chd", "oc", "dis"}
    assert "tdis" not in packages
    assert "gwf" not in packages
    assert "ims" not in packages


@pytest.mark.skipif(
    not any(_testmodels_repo_paths_mf6), reason="mf6 test models not found"
)
def test_get_packages_fails_on_invalid_namefile(module_tmpdir):
    model_path = _testmodels_repo_paths_mf6[0]
    new_model_path = module_tmpdir / model_path.name
    namefile_path = new_model_path / "mfsim.nam"
    shutil.copytree(model_path, new_model_path)

    # invalid gwf namefile reference - result should only contain packages from mfsim.nam
    lines = open(namefile_path, "r").read().splitlines()
    with open(namefile_path, "w") as f:
        for line in lines:
            if "GWF6" in line:
                line = line.replace("GWF6", "GWF6  garbage")
            f.write(line + os.linesep)
    assert set(get_packages(namefile_path)) == {"gwf", "tdis", "ims"}

    # entirely unparseable namefile - result should be empty
    lines = open(namefile_path, "r").read().splitlines()
    with open(namefile_path, "w") as f:
        for _ in lines:
            f.write("garbage" + os.linesep)
    assert not any(get_packages(namefile_path))


@pytest.mark.skipif(not any(_example_paths), reason="examples not found")
def test_has_package():
    model_path = _testmodels_repo_paths_mf6[0]
    namefile_path = model_path / "mfsim.nam"
    assert model_path.name == "test001a_Tharmonic"
    assert has_package(namefile_path, "tdis")
    assert has_package(namefile_path, "gwf")
    assert has_package(namefile_path, "ims")
    assert has_package(namefile_path, "npf")
    assert has_package(namefile_path, "chd")
    assert has_package(namefile_path, "oc")
    assert has_package(namefile_path, "dis")
    assert not has_package(namefile_path, "gwt")
    assert not has_package(namefile_path, "wel")


def get_expected_model_dirs(path, pattern="mfsim.nam") -> List[Path]:
    folders = []
    for root, dirs, files in os.walk(path):
        for d in dirs:
            p = Path(root) / d
            if any(p.glob(pattern)):
                folders.append(p)
    return sorted(list(set(folders)))


def get_expected_namefiles(path, pattern="mfsim.nam") -> List[Path]:
    folders = []
    for root, dirs, files in os.walk(path):
        for d in dirs:
            p = Path(root) / d
            found = list(p.glob(pattern))
            folders = folders + found
    return sorted(list(set(folders)))


@pytest.mark.skipif(
    not any(_example_paths), reason="modflow6-examples repo not found"
)
def test_get_model_paths_examples():
    expected_paths = get_expected_model_dirs(_examples_path)
    paths = get_model_paths(_examples_path)
    assert paths == sorted(list(set(paths)))  # no duplicates
    assert set(expected_paths) == set(paths)

    expected_paths = get_expected_model_dirs(_examples_path, "*.nam")
    paths = get_model_paths(_examples_path, namefile="*.nam")
    assert paths == sorted(list(set(paths)))
    assert set(expected_paths) == set(paths)


@pytest.mark.skipif(
    not any(_largetestmodel_paths), reason="modflow6-largetestmodels not found"
)
def test_get_model_paths_largetestmodels():
    expected_paths = get_expected_model_dirs(_examples_path)
    paths = get_model_paths(_examples_path)
    assert paths == sorted(list(set(paths)))
    assert set(expected_paths) == set(paths)

    expected_paths = get_expected_model_dirs(_examples_path)
    paths = get_model_paths(_examples_path)
    assert paths == sorted(list(set(paths)))
    assert set(expected_paths) == set(paths)


@pytest.mark.skipif(
    not any(_largetestmodel_paths) or not any(_example_paths),
    reason="repos not found",
)
@pytest.mark.parametrize(
    "models", [(_examples_path, 63), (_largetestmodels_repo_path, 15)]
)
def test_get_model_paths_exclude_patterns(models):
    path, expected_count = models
    paths = get_model_paths(path, excluded=["gwt"])
    assert len(paths) == expected_count


@pytest.mark.skipif(
    not any(_example_paths), reason="modflow6-examples repo not found"
)
def test_get_namefile_paths_examples():
    expected_paths = get_expected_namefiles(_examples_path)
    paths = get_namefile_paths(_examples_path)
    assert paths == sorted(list(set(paths)))
    assert set(expected_paths) == set(paths)

    expected_paths = get_expected_namefiles(_examples_path, "*.nam")
    paths = get_namefile_paths(_examples_path, namefile="*.nam")
    assert paths == sorted(list(set(paths)))
    assert set(expected_paths) == set(paths)


@pytest.mark.skipif(
    not any(_largetestmodel_paths), reason="modflow6-largetestmodels not found"
)
def test_get_namefile_paths_largetestmodels():
    expected_paths = get_expected_namefiles(_largetestmodels_repo_path)
    paths = get_namefile_paths(_largetestmodels_repo_path)
    assert paths == sorted(list(set(paths)))
    assert set(expected_paths) == set(paths)

    expected_paths = get_expected_namefiles(_largetestmodels_repo_path)
    paths = get_namefile_paths(_largetestmodels_repo_path)
    assert paths == sorted(list(set(paths)))
    assert set(expected_paths) == set(paths)


@pytest.mark.skipif(
    not any(_largetestmodel_paths) or not any(_example_paths),
    reason="repos not found",
)
@pytest.mark.parametrize(
    "models", [(_examples_path, 43), (_largetestmodels_repo_path, 18)]
)
def test_get_namefile_paths_exclude_patterns(models):
    path, expected_count = models
    paths = get_namefile_paths(path, excluded=["gwf"])
    assert len(paths) == expected_count


@pytest.mark.skipif(not any(_example_paths), reason="examples not found")
def test_get_namefile_paths_select_prefix():
    paths = get_namefile_paths(_examples_path, prefix="ex2")
    assert not any(paths)

    paths = get_namefile_paths(_examples_path, prefix="ex")
    assert any(paths)


@pytest.mark.skipif(not any(_example_paths), reason="examples not found")
def test_get_namefile_paths_select_patterns():
    paths = get_namefile_paths(_examples_path, selected=["gwf"])
    assert len(paths) == 70


@pytest.mark.skipif(not any(_example_paths), reason="examples not found")
def test_get_namefile_paths_select_packages():
    paths = get_namefile_paths(_examples_path, packages=["wel"])
    assert len(paths) == 43
