from pathlib import Path
from schema import Schema, And, Use, SchemaError
from src import pistachio


import json
import pytest
import shutil


# Schema ----------------------------------------------------------------------
TREE_SCHEMA = Schema({
    "path": And(Use(str)),
    "results": And(Use(list))
})


DESCRIBE_SCHEMA = Schema({
    "path": And(Use(str)),
    "exists": And(Use(bool)),
    "is_directory": And(Use(bool)),
    "is_file": And(Use(bool)),
    "is_symlink": And(Use(bool)),
    "name": And(Use(str)),
    "stem": And(Use(str)),
    "suffix": And(Use(str))
})


def schema_validation(example, schema):
    """
    Method to confirm with a dictionary matches a schema.
    """
    try:
        schema.validate(example)
        return True
    except SchemaError:
        return False


# State -----------------------------------------------------------------------
def setup_module():
    """
    Setup the required resources neccessary to run all the tests.
    """
    with open("example.txt", "w") as fh:
        fh.write("The quick brown fox jumps over the lazy dog\n")
        fh.close()

    Path("./tests/abc").mkdir()
    Path("./tests/abc/xyz").mkdir()

    Path("./tests/abc/file-1.txt").touch()
    Path("./tests/abc/file-2.txt").symlink_to("./tests/abc/file-1.txt")
    Path("./tests/abc/xyz/file-3.txt").touch()
    Path("./tests/abc/xyz/file-4.txt").touch()

    Path("./456").mkdir()
    Path("./456/789").mkdir()
    Path("./456/file-8.txt").touch()
    Path("./456/file-9.txt").symlink_to("./tests/abc/file-1.txt")
    Path("./file-6.txt").touch()
    Path("./file-7.txt").symlink_to("./tests/abc/file-1.txt")


def teardown_module():
    """
    Remove anything generated by testing.
    """
    Path("README.doc").unlink()
    Path("example.rtf").unlink()
    Path("example.txt").unlink()

    shutil.rmtree("./tests/abc/")
    shutil.rmtree("./tests/123/")


# Fixtures --------------------------------------------------------------------
@pytest.fixture
def tree_expected_results():
    return json.dumps(
        {
            "path": "./tests/abc",
            "results": [
                {
                    "path": "./def",
                    "exists": True,
                    "is_directory": True,
                    "is_file": False,
                    "is_symlink": False,
                    "name": "def",
                    "stem": "def",
                    "suffix": None
                },
                {
                    "path": "./file-1.txt",
                    "exists": True,
                    "is_directory": False,
                    "is_file": True,
                    "is_symlink": False,
                    "name": "file-1.txt",
                    "stem": "file-1",
                    "suffix": "txt"
                },
                {
                    "path": "./file-2.txt",
                    "exists": True,
                    "is_directory": False,
                    "is_file": False,
                    "is_symlink": True,
                    "name": "file-2.txt",
                    "stem": "file-2",
                    "suffix": "txt"
                },
                {
                    "path": "./file-6.txt",
                    "exists": True,
                    "is_directory": False,
                    "is_file": True,
                    "is_symlink": False,
                    "name": "file-6.txt",
                    "stem": "file-6",
                    "suffix": "txt"
                },
                {
                    "path": "./file-7.txt",
                    "exists": True,
                    "is_directory": False,
                    "is_file": False,
                    "is_symlink": True,
                    "name": "file-7.txt",
                    "stem": "file-7",
                    "suffix": "txt"
                },
                {
                    "path": "./ghi",
                    "exists": True,
                    "is_directory": True,
                    "is_file": False,
                    "is_symlink": False,
                    "name": "ghi",
                    "stem": "ghi",
                    "suffix": None
                },
                {
                    "path": "./ghi/jkl",
                    "exists": True,
                    "is_directory": True,
                    "is_file": False,
                    "is_symlink": False,
                    "name": "jkl",
                    "stem": "jkl",
                    "suffix": None
                },
                {
                    "path": "./xyz",
                    "exists": True,
                    "is_directory": True,
                    "is_file": False,
                    "is_symlink": False,
                    "name": "xyz",
                    "stem": "xyz",
                    "suffix": None
                },
                {
                    "path": "./xyz/file-3.txt",
                    "exists": True,
                    "is_directory": False,
                    "is_file": True,
                    "is_symlink": False,
                    "name": "file-3.txt",
                    "stem": "file-3",
                    "suffix": "txt"
                },
                {
                    "path": "./xyz/file-4.txt",
                    "exists": True,
                    "is_directory": False,
                    "is_file": True,
                    "is_symlink": False,
                    "name": "file-4.txt",
                    "stem": "file-4",
                    "suffix": "txt"
                }
            ]
        },
        indent=2,
        sort_keys=True
    )


# Tests -----------------------------------------------------------------------
def test_copy_file():
    """
    Test the method to copy and paste a file on the filesystem.
    """
    result = pistachio.cp("example.txt", "example.rtf")

    assert result is True
    assert Path("example.rtf").exists() is True


def test_copy_folder():
    """
    Test the method to copy and paste a file on the filesystem.
    """
    result = pistachio.cp("./tests/abc", "./tests/123")

    assert result is True
    assert Path("./tests/123").exists() is True


def test_copy_symlink():
    """
    Test the method to copy and paste a file on the filesystem.
    """
    result = pistachio.cp("./tests/abc/file-2.txt", "./tests/123/file-5.txt")

    assert result is True
    assert Path("./tests/123/file-5.txt").is_symlink() is True


def test_describe_schema_directory():
    """
    Test to validate the describe method response for a directory.
    """
    example = pistachio.describe("tests")
    assert schema_validation(example, DESCRIBE_SCHEMA) is True


def test_describe_schema_file():
    """
    Test to validate the describe method response for a file.
    """
    example = pistachio.describe("README.md")
    assert schema_validation(example, DESCRIBE_SCHEMA) is True


def test_describe_schema_symlink():
    """
    Test to validate the describe method response for a symbolic link.
    """
    example = pistachio.describe("README.rst")
    assert schema_validation(example, DESCRIBE_SCHEMA) is True


def test_exists_true():
    """
    Test to confirm the exists method returns True.
    """
    assert pistachio.exists("README.md") is True


def test_exists_false():
    """
    Test to confirm the exists method returns False.
    """
    assert pistachio.exists("LICENSE.md") is False


def test_get_md5_hash():
    """
    Test to confirm the get_md5_hash method returns False.
    """
    md5_hash_str = pistachio.get_md5_hash("example.txt")
    assert md5_hash_str == "37c4b87edffc5d198ff5a185cee7ee09"


def test_get_md5_hash_returns_none():
    """
    Test to confirm the get_md5_hash method returns None.
    """
    assert pistachio.get_md5_hash("example.docx") is None


def test_is_directory_true():
    """
    Test to confirm the is_directory method returns True.
    """
    assert pistachio.is_directory("tests") is True


def test_is_directory_false():
    """
    Test to confirm the is_directory method returns False.
    """
    assert pistachio.is_directory("LICENSE.md") is False


def test_is_file_true():
    """
    Test to confirm the is_file method returns True.
    """
    assert pistachio.is_file("README.md") is True


def test_is_file_false():
    """
    Test to confirm the is_file method returns False.
    """
    assert pistachio.is_file("tests") is False


def test_make_directory():
    """
    Method to verify that a directory can be created.
    """
    path_str = "./tests/abc/def"
    pistachio.mkdir(path_str)

    assert Path(path_str).exists() is True


def test_make_directory_recursively():
    """
    Method to verify that a directory path can be created recursively.
    """
    path_str = "./tests/abc/ghi/jkl"
    pistachio.mkdir(path_str)

    assert Path(path_str).exists() is True


def test_mv_directory():
    """
    Method to confirm that a directory can be moved.
    """
    result = pistachio.mv("./456", "./tests/123/456")

    assert result is True


def test_mv_file():
    """
    Method to confirm that a file can be moved.
    """
    result = pistachio.mv("./file-6.txt", "./tests/abc/file-6.txt")

    assert result is True


def test_mv_symlink():
    """
    Method to confirm that a symlink can be moved.
    """
    result = pistachio.mv("./file-7.txt", "./tests/abc/file-7.txt")

    assert result is True


def test_name_example_directory():
    """
    Test to confirm the name method returns "None".
    """
    assert pistachio.name("./foo/example") == "example"


def test_name_example_file():
    """
    Test to confirm the name method returns "example.txt".
    """
    assert pistachio.name("./foo/example.txt") == "example.txt"


def test_stem_example_directory():
    """
    Test to confirm the stem method returns "example".
    """
    assert pistachio.stem("./foo/example") == "example"


def test_stem_example_file():
    """
    Test to confirm the stem method returns "example".
    """
    assert pistachio.stem("./foo/example.txt") == "example"


def test_suffix_example_directory():
    """
    Test to confirm the suffix method returns "None".
    """
    assert pistachio.suffix("./foo/example") is None


def test_suffix_example_file():
    """
    Test to confirm the suffix method returns ".txt".
    """
    assert pistachio.suffix("./foo/example.txt") == "txt"


def test_touch_new_file_true():
    """
    Test to confirm the touch method returns True.
    """
    assert pistachio.touch("README.doc") is True


def test_touch_file_exist_returns_false():
    """
    Test to confirm the touch method returns True.
    """
    assert pistachio.touch("README.md") is False


def test_touch_directory_does_not_exist_returns_error():
    """
    Test to confirm the touch method raised FileNotFoundError exception.
    """
    with pytest.raises(FileNotFoundError):
        pistachio.touch("docs/README.doc")


def test_tree():
    """
    Test to confirm that the tree method returns a list of dictionaries.
    """
    r = pistachio.tree("tests/abc")

    assert schema_validation(r, TREE_SCHEMA) is True
    assert all(
        schema_validation(i, DESCRIBE_SCHEMA) is True for i in r["results"]
    )


def test_tree_results(tree_expected_results):
    """
    Test to confirm that the tree method returns a list of dictionaries.
    """
    results = json.dumps(
        pistachio.tree("./tests/abc"),
        indent=2,
        sort_keys=True
    )

    assert tree_expected_results == results


def test_tree_no_directory():
    """
    Test to confirm the tree method raised FileNotFoundError exception.
    """
    with pytest.raises(FileNotFoundError):
        pistachio.tree("docs")
