import backups.utils.subprocess_utils as utils
import os
import pytest
import stat

from pathlib import Path
from .errors import SubprocessError, ValidationError


def make_a_real_file(file_path, file_name):
    real_file = os.path.join(file_path, file_name)
    Path(real_file).touch()
    assert os.path.exists(real_file)
    assert os.path.isfile(real_file)
    return real_file


class TestCopyWrapper(object):

    def test_copy_raises_validation_error_with_no_args(self):
        with pytest.raises(ValidationError):
            utils.cp_wrapper(None)

    def test_copy_fails_with_invalid_args(self, tmpdir):
        with pytest.raises(SubprocessError):
            utils.cp_wrapper(["not a real file", "not a valid copy_location"])

    def test_copy_fails_if_source_does_not_exist(self, tmpdir):
        with pytest.raises(SubprocessError):
            utils.cp_wrapper(["not a real file", tmpdir])

    def test_simple_copy(self, tmpdir):
        real_file = make_a_real_file(tmpdir, "real_file.txt")
        expected_filepath = os.path.join(tmpdir, "copied_file.txt")
        assert not os.path.isfile(expected_filepath)

        result = utils.cp_wrapper(["-v", real_file, expected_filepath])
        assert os.path.isfile(expected_filepath)
        assert result == 0

    def test_simple_copy_to_directory(self, tmpdir):

        # Setup source file and target dir
        filename = "real_file.txt"
        real_file = make_a_real_file(tmpdir, filename)

        destination_dir = os.path.join(tmpdir, "destination")
        os.makedirs(destination_dir)
        assert os.path.exists(destination_dir)

        expected_filepath = os.path.join(destination_dir, filename)
        assert not os.path.exists(expected_filepath)

        # run copy function and confirm copy exists (and original)
        result = utils.cp_wrapper([real_file, destination_dir])
        assert result == 0
        assert os.path.isfile(real_file)
        assert os.path.exists(expected_filepath)

    def test_copying_directories_and_subdirectories(self, tmpdir):

        subdir = os.path.join(tmpdir, "subdir")
        subsubdir = os.path.join(subdir, "subsubdir")
        os.makedirs(subsubdir)
        filename_a = "file_in_subdir.txt"
        filename_b = "file_in_subsubdir.txt"
        make_a_real_file(subdir, filename_a)
        make_a_real_file(subsubdir, filename_b)

        destination_dir = os.path.join(tmpdir, "destination")
        os.makedirs(destination_dir)
        expected_filepath_a = os.path.join(destination_dir, "subdir", filename_a)
        expected_filepath_b = os.path.join(destination_dir, "subdir", "subsubdir", filename_b)
        assert not os.path.exists(expected_filepath_a)
        assert not os.path.exists(expected_filepath_b)

        # fails without -r arg:
        with pytest.raises(SubprocessError):
            utils.cp_wrapper([subdir, destination_dir])
        assert not os.path.exists(expected_filepath_a)
        assert not os.path.exists(expected_filepath_b)

        utils.cp_wrapper(["-r", subdir, destination_dir])
        assert os.path.exists(expected_filepath_a)
        assert os.path.exists(expected_filepath_b)


class TestTarWrapper(object):

    def test_tar_raise_validation_error_with_none_args(self):
        with pytest.raises(ValidationError):
            utils.tar_wrapper(None)

    def test_tar_returns_safely_with_too_few_args(self):
        with pytest.raises(SubprocessError):
            utils.tar_wrapper(["only one arg"])

    def test_tar_return_error_if_invalid_args_given(self, tmpdir):
        tar_filename = os.path.join(tmpdir, "tar_file.tar")
        fake_file = os.path.join(tmpdir, "fake_file.txt")
        assert not os.path.exists(fake_file)
        with pytest.raises(SubprocessError):
            utils.tar_wrapper(["-cf", tar_filename, fake_file])

    def test_simple_tar_of_files(self, tmpdir):
        file_a = make_a_real_file(tmpdir, "file_a.txt")
        file_b = make_a_real_file(tmpdir, "file_b.txt")

        expected_file = os.path.join(tmpdir, "output.tar")
        assert not os.path.exists(expected_file)

        args = ["-cf", expected_file, file_a, file_b]
        utils.tar_wrapper(args)
        assert os.path.exists(expected_file)


class TestChmodWrapper(object):

    def test_chmod_gives_validation_error_when_no_args(self):
        with pytest.raises(utils.ValidationError):
            utils.chmod_wrapper(None)

    def test_chmod_wrapper_raises_error_with_invalid_args(self):
        with pytest.raises(SubprocessError):
            utils.chmod_wrapper(["only one arg"])

    def test_chmod_fails_if_no_permissions_given(self, tmpdir):
        real_file = make_a_real_file(tmpdir, 'mr_file')

        with pytest.raises(SubprocessError):
            utils.chmod_wrapper([real_file])

        with pytest.raises(SubprocessError):
            utils.chmod_wrapper(["dumb_arg", real_file])

    def test_can_chmod_file(self, tmpdir):
        real_file = make_a_real_file(tmpdir, 'mr_file')

        # check initial permissions
        org_stats = os.stat(real_file)
        # Default should be: rw-rw-r--
        assert bool(org_stats.st_mode & stat.S_IRUSR) is True
        assert bool(org_stats.st_mode & stat.S_IWUSR) is True
        assert bool(org_stats.st_mode & stat.S_IXUSR) is False
        assert bool(org_stats.st_mode & stat.S_IRGRP) is True
        assert bool(org_stats.st_mode & stat.S_IWGRP) is True
        assert bool(org_stats.st_mode & stat.S_IXGRP) is False
        assert bool(org_stats.st_mode & stat.S_IROTH) is True
        assert bool(org_stats.st_mode & stat.S_IWOTH) is False
        assert bool(org_stats.st_mode & stat.S_IXOTH) is False

        # change them
        utils.chmod_wrapper(["777", real_file])

        # check they changed
        new_stats = os.stat(real_file)
        assert bool(new_stats.st_mode & stat.S_IRUSR) is True
        assert bool(new_stats.st_mode & stat.S_IWUSR) is True
        assert bool(new_stats.st_mode & stat.S_IXUSR) is True
        assert bool(new_stats.st_mode & stat.S_IRGRP) is True
        assert bool(new_stats.st_mode & stat.S_IWGRP) is True
        assert bool(new_stats.st_mode & stat.S_IXGRP) is True
        assert bool(new_stats.st_mode & stat.S_IROTH) is True
        assert bool(new_stats.st_mode & stat.S_IWOTH) is True
        assert bool(new_stats.st_mode & stat.S_IXOTH) is True


class TestLocalRsyncWrapper(object):
    """This is essentially a copy of the 'copy' tests above.
    """

    def test_rsync_raises_validation_error_with_no_args(self):
        with pytest.raises(ValidationError):
            utils.local_rsync_wrapper(None)

    def test_rsync_fails_with_invalid_args(self, tmpdir):
        with pytest.raises(SubprocessError):
            utils.local_rsync_wrapper(["not a real file", "not a valid rsync_location"])

    def test_rsync_fails_if_source_does_not_exist(self, tmpdir):
        with pytest.raises(SubprocessError):
            utils.local_rsync_wrapper(["not a real file", tmpdir])

    def test_simple_rsync(self, tmpdir):
        real_file = make_a_real_file(tmpdir, "real_file.txt")
        expected_filepath = os.path.join(tmpdir, "copied_file.txt")
        assert not os.path.isfile(expected_filepath)

        result = utils.local_rsync_wrapper(["-v", real_file, expected_filepath])
        assert os.path.isfile(expected_filepath)
        assert result == 0

    def test_simple_rsync_to_directory(self, tmpdir):

        # Setup source file and target dir
        filename = "real_file.txt"
        real_file = make_a_real_file(tmpdir, filename)

        destination_dir = os.path.join(tmpdir, "destination")
        os.makedirs(destination_dir)
        assert os.path.exists(destination_dir)

        expected_filepath = os.path.join(destination_dir, filename)
        assert not os.path.exists(expected_filepath)

        # run rsync function and confirm rsync exists (and original)
        result = utils.local_rsync_wrapper([real_file, destination_dir])
        assert result == 0
        assert os.path.isfile(real_file)
        assert os.path.exists(expected_filepath)

    def test_rsyncing_directories_and_subdirectories(self, tmpdir):

        subdir = os.path.join(tmpdir, "subdir")
        subsubdir = os.path.join(subdir, "subsubdir")
        os.makedirs(subsubdir)
        filename_a = "file_in_subdir.txt"
        filename_b = "file_in_subsubdir.txt"
        make_a_real_file(subdir, filename_a)
        make_a_real_file(subsubdir, filename_b)

        destination_dir = os.path.join(tmpdir, "destination")
        os.makedirs(destination_dir)
        expected_filepath_a = os.path.join(destination_dir, "subdir", filename_a)
        expected_filepath_b = os.path.join(destination_dir, "subdir", "subsubdir", filename_b)
        assert not os.path.exists(expected_filepath_a)
        assert not os.path.exists(expected_filepath_b)

        # silently passes without -r, just skips over directories
        assert not os.path.exists(expected_filepath_a)
        assert not os.path.exists(expected_filepath_b)

        utils.local_rsync_wrapper([subdir, destination_dir])
        assert not os.path.exists(expected_filepath_a)
        assert not os.path.exists(expected_filepath_b)

        # requires -r to copy directories (and contents)
        utils.local_rsync_wrapper(["-r", subdir, destination_dir])
        assert os.path.exists(expected_filepath_a)
        assert os.path.exists(expected_filepath_b)
