import configparser
import datetime
import os
import pytest

from pathlib import Path

import backup as bckup
import confluence_backup as cbck_mod

from confluence_backup import ConfluenceBackup
from utils.errors import ValidationError
from utils.config_parse_helper import ConfigSchema

CONF_TEST_CONFIG = os.path.join(os.path.dirname(__file__), './testing/confluence_test.config')


def make_config(tmpdir) -> configparser.ConfigParser:
    config = configparser.ConfigParser()

    config['DEFAULT'] = \
        { 'backup_paths' :  (f"{os.path.join(tmpdir, 'host_backup_path')},"
                             f"{os.path.join(tmpdir, 'backup_1_path')},"
                             f"{os.path.join(tmpdir, 'backup_2_path')}")
        , 'source_machine' : 'rocket'
        , 'verbose' : 'True'
        }
    config['COPY'] = \
        { 'sources' : f"{os.path.join(tmpdir, 'confluence_backup_path')}"
        }
    config['TAR'] = \
        { 'sources' : f"{os.path.join(tmpdir, 'confluence_attachments_path')}"
        , 'additional_args' : '-czf'
        , 'target_filename' : 'confluence.tar.gz'
        , 'prefix_date' : 'True'
        }
    config['RSYNC'] = \
        { 'sources' : (f"{os.path.join(tmpdir, 'rsync_source_dir1')},"
                       f"{os.path.join(tmpdir, 'rsync_source_dir2')}")
        , 'additional_args' : '-a --delete-after --exclude exclude_me_if_you_like'
        , 'sub-dirs' : 'conf_subdir1, conf_subdir2'
        }
    config['MYSQLDUMP'] = \
        { 'user' : 'dumper'
        , 'password' : 'not_a_real_password'
        , 'db_name' : 'confluencedb'
        , 'target_filename' : 'confluence_mysqldump.sql'
        , 'prefix_date' : 'True'
        }
    return config


def create_items_for_full_confluence_backup_test(tmpdir):
    """This creates all paths and files needed for thorough testing of the
ConfluenceBackup class.
"""

    host_backup_path = os.path.join(tmpdir, "host_backup_path")
    backup_1_path = os.path.join(tmpdir, "backup_1_path")
    backup_2_path = os.path.join(tmpdir, "backup_2_path")
    confluence_backup_path = os.path.join(tmpdir, "confluence_backup_path")
    confluence_attachments_path = os.path.join(tmpdir, "confluence_attachments_path")
    os.makedirs(host_backup_path)
    os.makedirs(backup_1_path)
    os.makedirs(backup_2_path)
    os.makedirs(confluence_backup_path)
    os.makedirs(confluence_attachments_path)
    assert os.path.exists(host_backup_path)
    assert os.path.exists(backup_1_path)
    assert os.path.exists(backup_2_path)
    assert os.path.exists(confluence_backup_path)
    assert os.path.exists(confluence_attachments_path)

    # Create backup files that will be copied
    confluence_backup_name = f"confluence-xml-backup-{ConfluenceBackup.get_confluence_backup_date_format()}.zip"
    confluence_backup_filepath = os.path.join(confluence_backup_path, confluence_backup_name)
    Path(confluence_backup_filepath).touch()
    assert os.path.exists(confluence_backup_filepath)

    # Create some files that will be tar'd
    confluence_attachment_1_name = "attachment_1.zip"
    confluence_attachment_2_name = "attachment_2.txt"
    confluence_attachment_1_filepath = os.path.join(confluence_attachments_path, confluence_attachment_1_name)
    confluence_attachment_2_filepath = os.path.join(confluence_attachments_path, confluence_attachment_2_name)
    Path(confluence_attachment_1_filepath).touch()
    Path(confluence_attachment_2_filepath).touch()
    assert os.path.exists(confluence_attachment_1_filepath)
    assert os.path.exists(confluence_attachment_2_filepath)

    # Create a few folders to be rsync'd
    rsync_source_dir1 = os.path.join(tmpdir, "rsync_source_dir1")
    rsync_source_dir2 = os.path.join(tmpdir, "rsync_source_dir2")
    file_in_rsync_dir1 = os.path.join(rsync_source_dir1, "some_file_to_rsync")
    file_in_rsync_dir2 = os.path.join(rsync_source_dir2, "some_other_file_to_rsync")
    exluded_file = os.path.join(rsync_source_dir1, "exclude_me_if_you_like")
    os.makedirs(rsync_source_dir1)
    os.makedirs(rsync_source_dir2)
    Path(file_in_rsync_dir1).touch()
    Path(file_in_rsync_dir2).touch()
    Path(exluded_file).touch()
    assert os.path.exists(file_in_rsync_dir1)
    assert os.path.exists(file_in_rsync_dir2)
    assert os.path.exists(exluded_file)


class TestArguments(object):

    def assert_defaults(self, jb: ConfluenceBackup):

        assert jb.default_dict == \
            { 'backup_paths' :  [ '/export/ippops4.0/backups/confluence_backups'
                                , '/data/ippops3.0/backups/confluence_backups'
                                , '/data/ippops5.0/backups/confluence_backups'
                                ]
            , 'source_machine' : 'ippops4'
            , 'verbose' : False
            }
        assert jb.copy_dict == \
            { 'sources' : ['/var/atlassian/application-data/confluence/backups']
            , 'additional_args' : ['-v']
            }
        assert jb.tar_dict == \
            { 'sources' : ['/var/atlassian/application-data/confluence/attachments']
            , 'additional_args' : ['--verbose', '-z', '-p', '-cf']
            , 'target_filename' : 'confluence.tar.gz'
            , 'prefix_date' : True
            }
        assert jb.mysqldump_dict == \
            { 'user' : 'dumper'
            , 'password' : 'not_a_real_password'
            , 'db_name' : 'confluencedb'
            , 'target_filename' : 'confluence_mysqldump.sql'
            , 'prefix_date' : True
            }

    def test_load_from_config_file(self):
        jb = ConfluenceBackup(config_file=CONF_TEST_CONFIG)
        self.assert_defaults(jb)

    def test_default_config_fails_to_load_if_the_file_does_not_exist(self):
        default_config = cbck_mod.EXPECTED_CONFIG_FILE
        assert not os.path.exists(default_config)

        with pytest.raises(ValidationError) as e:
            ConfluenceBackup()
        assert "ValidationError: Config filepath does not exist" in str(e)

    def test_default_arguments(self):
        jb = ConfluenceBackup(config_file=CONF_TEST_CONFIG)
        self.assert_defaults(jb)

    def test_providing_config_directly(self):
        config = configparser.ConfigParser()
        config.add_section("atlassian_backups")
        config['DEFAULT'] = \
            { 'backup_paths' :  "/path/1, /path/2"
            , 'source_machine' : 'rocket'
            , 'verbose' : 'True'
            }
        config['COPY'] = \
            { 'sources' : "/copy/source"
            }
        config['TAR'] = \
            { 'sources' : "/tar/source"
            , 'additional_args' : "--verbose"
            , 'target_filename' : 'confluence.tar.gz'
            , 'prefix_date' : 'False'
            }

        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        schema.add_section(bckup.COPY_SCHEMA_SECTION)
        schema.add_section(bckup.TAR_SCHEMA_SECTION)

        jb = ConfluenceBackup(config, schema)

        assert jb.default_dict == \
            { 'backup_paths' :  [ '/path/1'
                                , '/path/2'
                                ]
            , 'source_machine' : 'rocket'
            , 'verbose' : True
            }

        assert jb.copy_dict == \
            { 'sources' : ['/copy/source']
            }

        assert jb.tar_dict == \
            { 'sources' : ['/tar/source']
            , 'additional_args' : ['--verbose']
            , 'target_filename' : 'confluence.tar.gz'
            , 'prefix_date' : False
            }

    def test_nonexistant_config_file_raises_error(self):
        with pytest.raises(ValidationError) as e:
            ConfluenceBackup("not a real file path")
        assert "ValidationError: Config filepath does not exist" in str(e)

    def test_main_loads_default_and_fails_due_to_missing_paths(self):
        with pytest.raises(ValidationError) as e:
            cbck_mod.main()
        assert "ValidationError: Config filepath does not exist" in str(e)


class TestFullRunFromMain(object):

    def test_main_loads_and_runs_fine_when_given_config(self, tmpdir):
        config = configparser.ConfigParser()
        bpath1 = os.path.join(tmpdir, "path/1")
        bpath2 = os.path.join(tmpdir, "path/2")
        config['DEFAULT'] = \
            { 'backup_paths' :  f"{bpath1}, {bpath2}"
            , 'source_machine' : 'rocket'
            , 'verbose' : 'True'
            }
        copy_source = os.path.join(tmpdir, "copy/source")
        conf_backup_name = ConfluenceBackup.get_confluence_backup_date_format() + "_conf_test_file.zip"
        config['COPY'] = \
            { 'sources' : f'{copy_source}'
            }
        tar_source =  os.path.join(tmpdir, "tar/source")
        config['TAR'] = \
            { 'sources' : f"{tar_source}"
            , 'additional_args' : "--verbose -z -p -cf"
            , 'target_filename' : 'conf.tar.gz'
            , 'prefix_date' : 'False'
            }
        os.makedirs(bpath1)
        os.makedirs(bpath2)
        os.makedirs(copy_source)
        os.makedirs(tar_source)
        Path(os.path.join(copy_source, conf_backup_name)).touch()

        schema = ConfigSchema(None)
        schema.add_section(bckup.DEFAULT_SCHEMA_SECTION)
        schema.add_section(bckup.COPY_SCHEMA_SECTION)
        schema.add_section(bckup.TAR_SCHEMA_SECTION)

        cbck_mod.main(config, schema)
        assert True is True


class TestCopyingBackups(object):

    # This should behanve differently to the regular backups:

    def test_get_confluence_backup_date_format(self):
        cb = ConfluenceBackup(config_file=CONF_TEST_CONFIG)
        today = datetime.date.today()
        expected_format = today.strftime("%Y_%m_%d")
        assert cb.get_confluence_backup_date_format() == expected_format

        another_date = datetime.date(1988, 11, 23)
        expected_format = another_date.strftime("%Y_%m_%d")
        assert cb.get_confluence_backup_date_format(another_date) == expected_format

    def test_copying_fails_if_paths_not_present(self):
        cb = ConfluenceBackup(config_file=CONF_TEST_CONFIG)
        # return_value = cb._copy_confluence_backup()
        with pytest.raises(ValidationError) as e:
            cb._run_copy()
        assert "ValidationError: target path does not exist:" in str(e)

    def test_confluence_copy(self, tmpdir):
        create_items_for_full_confluence_backup_test(tmpdir)
        config = make_config(tmpdir)
        cb = ConfluenceBackup(config_file=config)

        today = ConfluenceBackup.get_confluence_backup_date_format()
        expected_host_backup_copy = os.path.join(tmpdir, "host_backup_path", f"confluence-xml-backup-{today}.zip" )
        expected_backup_1_copy    = os.path.join(tmpdir, "backup_1_path",    f"confluence-xml-backup-{today}.zip" )
        expected_backup_2_copy    = os.path.join(tmpdir, "backup_2_path",    f"confluence-xml-backup-{today}.zip" )
        assert not os.path.exists(expected_host_backup_copy)
        assert not os.path.exists(expected_backup_1_copy)
        assert not os.path.exists(expected_backup_2_copy)

        return_value = cb._run_copy()
        assert return_value == 0

        assert os.path.exists(expected_host_backup_copy)
        assert os.path.exists(expected_backup_1_copy)
        assert os.path.exists(expected_backup_2_copy)


class TestTaringOfAttachments(object):

    def test_confluence_attachments_tar(self, tmpdir):
        """This should be identical to the regular Backup version"""
        create_items_for_full_confluence_backup_test(tmpdir)
        config = make_config(tmpdir)
        jb = ConfluenceBackup(config_file=config)

        today = datetime.date.today().strftime("%Y_%m_%d")
        confluence_tar_name = f"{today}_confluence.tar.gz"
        expected_confluence_host_tar = os.path.join(tmpdir, "host_backup_path", confluence_tar_name)
        expected_confluence_bak1_tar = os.path.join(tmpdir, "backup_1_path",    confluence_tar_name)
        expected_confluence_bak2_tar = os.path.join(tmpdir, "backup_2_path",    confluence_tar_name)
        assert not os.path.exists(expected_confluence_host_tar)
        assert not os.path.exists(expected_confluence_bak1_tar)
        assert not os.path.exists(expected_confluence_bak2_tar)

        tar_result = jb._run_tar()
        assert tar_result == 0

        assert os.path.exists(expected_confluence_host_tar)
        assert os.path.exists(expected_confluence_bak1_tar)
        assert os.path.exists(expected_confluence_bak2_tar)


class TestRsyncOfFiles(object):

    def test_confluence_rsync_with_subdirs(self, tmpdir):
        """This should be similar to the regular Backup version"""
        create_items_for_full_confluence_backup_test(tmpdir)
        config = make_config(tmpdir)

        cb = ConfluenceBackup(config_file=config)

        # Expected resulting rsync directories
        expected_confluence_host_rsync1 = os.path.join(tmpdir, "host_backup_path", "conf_subdir1", "rsync_source_dir1")
        expected_confluence_bak1_rsync1 = os.path.join(tmpdir, "backup_1_path",    "conf_subdir1", "rsync_source_dir1")
        expected_confluence_bak2_rsync1 = os.path.join(tmpdir, "backup_2_path",    "conf_subdir1", "rsync_source_dir1")
        expected_confluence_host_rsync2 = os.path.join(tmpdir, "host_backup_path", "conf_subdir2", "rsync_source_dir2")
        expected_confluence_bak1_rsync2 = os.path.join(tmpdir, "backup_1_path",    "conf_subdir2", "rsync_source_dir2")
        expected_confluence_bak2_rsync2 = os.path.join(tmpdir, "backup_2_path",    "conf_subdir2", "rsync_source_dir2")
        assert not os.path.exists(expected_confluence_host_rsync1)
        assert not os.path.exists(expected_confluence_bak1_rsync1)
        assert not os.path.exists(expected_confluence_bak2_rsync1)
        assert not os.path.exists(expected_confluence_host_rsync2)
        assert not os.path.exists(expected_confluence_bak1_rsync2)
        assert not os.path.exists(expected_confluence_bak2_rsync2)
        # Expected resulting rsync files
        expected_file_host_dir1 = os.path.join( expected_confluence_host_rsync1, "some_file_to_rsync")
        expected_file_bak1_dir1 = os.path.join( expected_confluence_bak1_rsync1, "some_file_to_rsync")
        expected_file_bak2_dir1 = os.path.join( expected_confluence_bak2_rsync1, "some_file_to_rsync")
        expected_file_host_dir2 = os.path.join( expected_confluence_host_rsync2, "some_other_file_to_rsync")
        expected_file_bak1_dir2 = os.path.join( expected_confluence_bak1_rsync2, "some_other_file_to_rsync")
        expected_file_bak2_dir2 = os.path.join( expected_confluence_bak2_rsync2, "some_other_file_to_rsync")
        assert not os.path.exists(expected_file_host_dir1)
        assert not os.path.exists(expected_file_bak1_dir1)
        assert not os.path.exists(expected_file_bak2_dir1)
        assert not os.path.exists(expected_file_host_dir2)
        assert not os.path.exists(expected_file_bak1_dir2)
        assert not os.path.exists(expected_file_bak2_dir2)
        an_excluded_file = os.path.join(expected_confluence_host_rsync1, "exclude_me_if_you_like")
        assert not os.path.exists(an_excluded_file)

        rsync_result = cb._run_rsync()
        assert rsync_result == 0

        # All files Rsync'd
        assert os.path.exists(expected_confluence_host_rsync1)
        assert os.path.exists(expected_confluence_bak1_rsync1)
        assert os.path.exists(expected_confluence_bak2_rsync1)
        assert os.path.exists(expected_confluence_host_rsync2)
        assert os.path.exists(expected_confluence_bak1_rsync2)
        assert os.path.exists(expected_confluence_bak2_rsync2)
        assert os.path.exists(expected_file_host_dir1)
        assert os.path.exists(expected_file_bak1_dir1)
        assert os.path.exists(expected_file_bak2_dir1)
        assert os.path.exists(expected_file_host_dir2)
        assert os.path.exists(expected_file_bak1_dir2)
        assert os.path.exists(expected_file_bak2_dir2)
        assert not os.path.exists(an_excluded_file)
