import configparser
import datetime
import os
import pytest

from pathlib import Path

import backups.backup as bckup
import jira_backup as jbck_mod
from jira_backup import JiraBackup
from utils.errors import ValidationError
from utils.config_parse_helper import ConfigSchema

JIRA_TEST_CONFIG = os.path.join(os.path.dirname(__file__), './testing/jira_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, 'jira_backup_path')}"
        }
    config['TAR'] = \
        { 'sources' : f"{os.path.join(tmpdir, 'jira_attachments_path')}"
        , 'additional_args' : '-czf'
        , 'target_filename' : 'jira.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'
        , 'sub-dirs' : 'jira_subdir1, jira_subdir2'
        }
    config['MYSQLDUMP'] = \
        { 'user' : 'dumper'
        , 'password' : 'not_a_real_password'
        , 'db_name' : 'jiradb'
        , 'target_filename' : 'jira_mysqldump.sql'
        , 'prefix_date' : 'True'
        }
    return config


def create_items_for_full_jira_backup_test(tmpdir):
    """This creates all paths and files needed for thorough testing of the
JiraBackup 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")
    jira_backup_path = os.path.join(tmpdir, "jira_backup_path")
    jira_attachments_path = os.path.join(tmpdir, "jira_attachments_path")
    os.makedirs(host_backup_path)
    os.makedirs(backup_1_path)
    os.makedirs(backup_2_path)
    os.makedirs(jira_backup_path)
    os.makedirs(jira_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(jira_backup_path)
    assert os.path.exists(jira_attachments_path)

    # Create backup files that will be copied
    jira_backup_name = JiraBackup.get_jira_backup_date_format() + "_jira_test_file.zip"
    jira_backup_filepath = os.path.join(jira_backup_path, jira_backup_name)
    Path(jira_backup_filepath).touch()
    assert os.path.exists(jira_backup_filepath)

    # Create some files that will be tar'd
    jira_attachment_1_name = "attachment_1.zip"
    jira_attachment_2_name = "attachment_2.txt"
    jira_attachment_1_filepath = os.path.join(jira_attachments_path, jira_attachment_1_name)
    jira_attachment_2_filepath = os.path.join(jira_attachments_path, jira_attachment_2_name)
    Path(jira_attachment_1_filepath).touch()
    Path(jira_attachment_2_filepath).touch()
    assert os.path.exists(jira_attachment_1_filepath)
    assert os.path.exists(jira_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")
    os.makedirs(rsync_source_dir1)
    os.makedirs(rsync_source_dir2)
    Path(file_in_rsync_dir1).touch()
    Path(file_in_rsync_dir2).touch()
    assert os.path.exists(file_in_rsync_dir1)
    assert os.path.exists(file_in_rsync_dir2)


class TestArguments(object):

    def assert_defaults(self, jb: JiraBackup):

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

    def test_load_from_config_file(self):
        jb = JiraBackup(config_file=JIRA_TEST_CONFIG)
        self.assert_defaults(jb)

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

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

    def test_default_arguments(self):
        jb = JiraBackup(config_file=JIRA_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' : 'jira.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 = JiraBackup(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' : 'jira.tar.gz'
            , 'prefix_date' : False
            }

    def test_nonexistant_config_file_raises_error(self):
        with pytest.raises(ValidationError) as e:
            JiraBackup("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:
            jbck_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()
        config.add_section("atlassian_backups")
        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")
        jira_backup_name = JiraBackup.get_jira_backup_date_format() + "_jira_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' : 'jira.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, jira_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)

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


class TestCopyingBackups(object):

    # This should behanve differently to the regular backups:

    def test_get_jira_backup_date_format(self):
        jb = JiraBackup(config_file=JIRA_TEST_CONFIG)
        today = datetime.date.today()
        expected_format = today.strftime("%Y-%b-%d")
        assert jb.get_jira_backup_date_format() == expected_format

        another_date = datetime.date(1988, 11, 23)
        expected_format = another_date.strftime("%Y-%b-%d")
        assert jb.get_jira_backup_date_format(another_date) == expected_format

    def test_copying_fails_if_paths_not_present(self):
        jb = JiraBackup(config_file=JIRA_TEST_CONFIG)
        # return_value = jb._copy_jira_backup()
        with pytest.raises(ValidationError) as e:
            jb._run_copy()
        assert "ValidationError: target path does not exist:" in str(e)

    def test_jira_copy(self, tmpdir):
        create_items_for_full_jira_backup_test(tmpdir)
        config = make_config(tmpdir)
        jb = JiraBackup(config_file=config)

        today = JiraBackup.get_jira_backup_date_format()
        expected_host_backup_copy = os.path.join(tmpdir, "host_backup_path", f"{today}_jira_test_file.zip" )
        expected_backup_1_copy    = os.path.join(tmpdir, "backup_1_path",    f"{today}_jira_test_file.zip" )
        expected_backup_2_copy    = os.path.join(tmpdir, "backup_2_path",    f"{today}_jira_test_file.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)

        assert jb.should_run_copy is True
        return_value = jb._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_jira_attachments_tar(self, tmpdir):
        """This should be identical to the regular Backup version"""
        create_items_for_full_jira_backup_test(tmpdir)
        config = make_config(tmpdir)
        jb = JiraBackup(config_file=config)

        today = datetime.date.today().strftime("%Y_%m_%d")
        jira_tar_name = f"{today}_jira.tar.gz"
        expected_jira_host_tar = os.path.join(tmpdir, "host_backup_path", jira_tar_name)
        expected_jira_bak1_tar = os.path.join(tmpdir, "backup_1_path",    jira_tar_name)
        expected_jira_bak2_tar = os.path.join(tmpdir, "backup_2_path",    jira_tar_name)
        assert not os.path.exists(expected_jira_host_tar)
        assert not os.path.exists(expected_jira_bak1_tar)
        assert not os.path.exists(expected_jira_bak2_tar)

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

        assert os.path.exists(expected_jira_host_tar)
        assert os.path.exists(expected_jira_bak1_tar)
        assert os.path.exists(expected_jira_bak2_tar)


class TestRsyncOfFiles(object):

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

        jb = JiraBackup(config_file=config)

        # Expected resulting rsync directories
        expected_jira_host_rsync1 = os.path.join(tmpdir, "host_backup_path", "jira_subdir1", "rsync_source_dir1")
        expected_jira_bak1_rsync1 = os.path.join(tmpdir, "backup_1_path",    "jira_subdir1", "rsync_source_dir1")
        expected_jira_bak2_rsync1 = os.path.join(tmpdir, "backup_2_path",    "jira_subdir1", "rsync_source_dir1")
        expected_jira_host_rsync2 = os.path.join(tmpdir, "host_backup_path", "jira_subdir2", "rsync_source_dir2")
        expected_jira_bak1_rsync2 = os.path.join(tmpdir, "backup_1_path",    "jira_subdir2", "rsync_source_dir2")
        expected_jira_bak2_rsync2 = os.path.join(tmpdir, "backup_2_path",    "jira_subdir2", "rsync_source_dir2")
        assert not os.path.exists(expected_jira_host_rsync1)
        assert not os.path.exists(expected_jira_bak1_rsync1)
        assert not os.path.exists(expected_jira_bak2_rsync1)
        assert not os.path.exists(expected_jira_host_rsync2)
        assert not os.path.exists(expected_jira_bak1_rsync2)
        assert not os.path.exists(expected_jira_bak2_rsync2)
        # Expected resulting rsync files
        expected_file_host_dir1 = os.path.join( expected_jira_host_rsync1, "some_file_to_rsync")
        expected_file_bak1_dir1 = os.path.join( expected_jira_bak1_rsync1, "some_file_to_rsync")
        expected_file_bak2_dir1 = os.path.join( expected_jira_bak2_rsync1, "some_file_to_rsync")
        expected_file_host_dir2 = os.path.join( expected_jira_host_rsync2, "some_other_file_to_rsync")
        expected_file_bak1_dir2 = os.path.join( expected_jira_bak1_rsync2, "some_other_file_to_rsync")
        expected_file_bak2_dir2 = os.path.join( expected_jira_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)

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

        # All files Rsync'd
        assert os.path.exists(expected_jira_host_rsync1)
        assert os.path.exists(expected_jira_bak1_rsync1)
        assert os.path.exists(expected_jira_bak2_rsync1)
        assert os.path.exists(expected_jira_host_rsync2)
        assert os.path.exists(expected_jira_bak1_rsync2)
        assert os.path.exists(expected_jira_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)
