import os
import pytest

from .generic_utils import *
from .errors import ValidationError
from pathlib import Path


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 TestCopyingToDir(object):

    def test_copy_fails_with_none_args(self, tmpdir):
        with pytest.raises(ValidationError):
            copy_to_dir(None, tmpdir)

        with pytest.raises(ValidationError):
            real_file = make_a_real_file(tmpdir, "file.txt")
            copy_to_dir(real_file, None)

    def test_copy_fails_if_source_does_not_exist(self, tmpdir):
        with pytest.raises(ValidationError):
            copy_to_dir("not a real file", tmpdir)

    def test_copy_fails_if_destination_is_not_real(self, tmpdir):
        with pytest.raises(ValidationError):
            real_file = make_a_real_file(tmpdir, "real_file.txt")
            copy_to_dir(real_file, "not a real directory")

    def test_copy_fails_if_destination_is_not_a_directory(self, tmpdir):

        real_file = make_a_real_file(tmpdir, "not_a_directory.txt")
        not_a_directory = real_file
        assert os.path.exists(not_a_directory)
        assert not os.path.isdir(not_a_directory)

        with pytest.raises(ValidationError):
            copy_to_dir(real_file, not_a_directory)

    def test_copy_to_dir(self, tmpdir):

        # Setup source file and target dir
        destination_dir = os.path.join(tmpdir, "destination")
        os.makedirs(destination_dir)
        assert os.path.exists(destination_dir)
        file_name = "fun_file.txt"
        file_to_copy = os.path.join(tmpdir, file_name)
        Path(file_to_copy).touch()
        assert os.path.exists(file_to_copy)

        # copied file does not exist yet
        expected_copy = os.path.join(tmpdir, "destination", file_name)
        assert not os.path.exists(expected_copy)

        # run copy function and confirm copy exists (and original)
        result = copy_to_dir(file_to_copy, destination_dir)
        assert result == 0
        assert os.path.exists(file_to_copy)
        assert os.path.exists(expected_copy)

    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"
        filepath_a = make_a_real_file(subdir, filename_a)
        filepath_b = make_a_real_file(subsubdir, filename_b)

        print(filepath_a)
        print(filepath_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)
        print(expected_filepath_a)
        print(expected_filepath_b)
        print(os.path.exists(expected_filepath_a))
        if os.path.exists(expected_filepath_a):
            print("apple")
        else:
            print("banana")
        print(os.path.exists(expected_filepath_b))

        os.path.commonpath

        assert not os.path.exists(expected_filepath_a)
        assert not os.path.exists(expected_filepath_b)

        # Provide only the original tmpdir to the copying function
        copy_to_dir(subdir, destination_dir, True)

        # assert that the subdirs and files all exist
        assert not os.path.exists(expected_filepath_a)
        assert not os.path.exists(expected_filepath_b)


class TestMultiCopy(object):

    def test_copy_fails_if_args_are_none(self, tmpdir):

        with pytest.raises(ValidationError):
            copy_multiple_items_to_multiple_dirs(None, None)

        with pytest.raises(ValidationError):
            copy_multiple_items_to_multiple_dirs(tmpdir, None)

        with pytest.raises(ValidationError):
            copy_multiple_items_to_multiple_dirs(None, tmpdir)

    def test_copy_requires_args_to_be_in_list_format(self, tmpdir):

        not_a_list = "not a list"
        with pytest.raises(ValidationError):
            copy_multiple_items_to_multiple_dirs(not_a_list, tmpdir)

        tmpfile = make_a_real_file(tmpdir, "a_file.txt")
        with pytest.raises(ValidationError):
            copy_multiple_items_to_multiple_dirs(tmpfile, not_a_list)

    def test_copy_is_fine_with_single_items(self, tmpdir):

        tmpfile = make_a_real_file(tmpdir, "single_file.txt")
        destination = os.path.join(tmpdir, "destination")
        os.makedirs(destination)
        expected_file = os.path.join(tmpdir, "destination", "single_file.txt")
        assert not os.path.exists(expected_file)

        result = copy_multiple_items_to_multiple_dirs([tmpfile], [destination])
        assert result == 0
        assert os.path.exists(expected_file)

    def test_copy_multiple_items_to_multiple_dirs(self, tmpdir):

        file_a = make_a_real_file(tmpdir, "single_file.txt")
        file_b = make_a_real_file(tmpdir, "single_file.txt")
        file_c = make_a_real_file(tmpdir, "single_file.txt")
        source_files = [ file_a
                       , file_b
                       , file_c
                       ]

        destination_1 = os.path.join(tmpdir, "dest_1")
        destination_2 = os.path.join(tmpdir, "dest_2")
        destination_3 = os.path.join(tmpdir, "dest_3")
        dest_paths = [ destination_1
                     , destination_2
                     , destination_3
                     ]
        os.makedirs(destination_1)
        os.makedirs(destination_2)
        os.makedirs(destination_3)

        expected_files = []
        for dp in dest_paths:
            for sf in source_files:
                expected_files.append( os.path.join(dp, os.path.basename(sf)))
                assert not os.path.exists(expected_files[-1])
        assert len(expected_files) == 9

        result = copy_multiple_items_to_multiple_dirs(source_files, dest_paths)
        assert result == 0
        for ef in expected_files:
            assert os.path.exists(ef)

    def test_copying_is_recursive(self, tmpdir):

        # make some subdirs, with sub-subdirs, and files within them
        subdir_a = os.path.join(tmpdir, "subdir_a")
        subsubdir_aa = os.path.join(subdir_a, "subsubdir_aa")
        subsubdir_ab = os.path.join(subdir_a, "subsubdir_ab")
        subdir_b = os.path.join(tmpdir, "subdir_b")
        subsubdir_ba = os.path.join(subdir_b, "subsubdir_ba")
        subsubdir_bb = os.path.join(subdir_b, "subsubdir_bb")
        os.makedirs(subsubdir_aa)
        os.makedirs(subsubdir_ab)
        os.makedirs(subsubdir_ba)
        os.makedirs(subsubdir_bb)
        make_a_real_file(subdir_a, "file_a.txt")
        make_a_real_file(subsubdir_aa, "file_aa.txt")
        make_a_real_file(subsubdir_ab, "file_ab.txt")
        make_a_real_file(subdir_b, "file_b.txt")
        make_a_real_file(subsubdir_ba, "file_ba.txt")
        make_a_real_file(subsubdir_bb, "file_bb.txt")

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

        expected_filepath_a = os.path.join(destination_dir, "subdir_a", "file_a.txt")
        expected_filepath_aa = os.path.join(destination_dir, "subdir_a", "subsubdir_aa", "file_aa.txt")
        expected_filepath_ab = os.path.join(destination_dir, "subdir_a", "subsubdir_ab", "file_ab.txt")
        expected_filepath_b = os.path.join(destination_dir, "subdir_b", "file_b.txt")
        expected_filepath_ba = os.path.join(destination_dir, "subdir_b", "subsubdir_ba", "file_ba.txt")
        expected_filepath_bb = os.path.join(destination_dir, "subdir_b", "subsubdir_bb", "file_bb.txt")
        assert not os.path.exists(expected_filepath_a)
        assert not os.path.exists(expected_filepath_b)
        assert not os.path.exists(expected_filepath_a)
        assert not os.path.exists(expected_filepath_aa)
        assert not os.path.exists(expected_filepath_ab)
        assert not os.path.exists(expected_filepath_b)
        assert not os.path.exists(expected_filepath_ba)
        assert not os.path.exists(expected_filepath_bb)

        # Provide only the original tmpdir to the copying function
        copy_multiple_items_to_multiple_dirs([tmpdir], [destination_dir], True)

        # assert that the subdirs and files all exist
        assert os.path.exists(expected_filepath_a)
        assert os.path.exists(expected_filepath_b)
        assert os.path.exists(expected_filepath_a)
        assert os.path.exists(expected_filepath_aa)
        assert os.path.exists(expected_filepath_ab)
        assert os.path.exists(expected_filepath_b)
        assert os.path.exists(expected_filepath_ba)
        assert os.path.exists(expected_filepath_bb)
