import datetime
import os
import pandas as pd
import pytest
import utils.db_handling.db_access
from utils.databases import Databases

from utils.databases.superclasses.database_superclasses import Database
from utils.db_handling.db_access import DbAccessor, DbObjBuilder


testdata = [
    ('tests/test_dbs/ncbi_nr/2018-08-08/db_config.yaml', '2018-08-08', 'ncbi_nr', 'tax', True),
    ('tests/test_dbs/nr/2018-10-08/db_config.yaml', '2018-10-08', 'ncbi_nr', 'tax', False),
    ('tests/test_dbs/pfams/31/db_config.yaml', '31', 'pfams', 'func', True),
    ('tests/test_dbs/uniprot_sp/2018-08-08/db_config.yaml', '2018-08-08', 'uniprot_sp', 'tax', True),
    ('tests/test_dbs/tigrfams/15.0/db_config.yaml', '15.0', 'tigrfams', 'func', True),
    ('tests/test_dbs/uniprot_tr/2018-08-08/db_config.yaml', '2018-08-08', 'uniprot_tr', 'tax', True),
    ('tests/test_dbs/uniprot_complete/2018-08-08/db_config.yaml', '2018-08-08', 'uniprot_complete', 'tax', True)
]
testdata_df = pd.DataFrame(testdata, columns=['db_yaml', 'db_version', 'db_type', 'scope', 'exists'])


@pytest.fixture()
def db_accessor(
        mocker,
        db_base_path="testing",
        test_df=testdata_df.copy()
):
    test_df = test_df[test_df['exists']][['db_type', 'scope', 'db_version', 'db_yaml']]
    test_df['db_yaml'] = test_df['db_yaml'].apply(lambda x: os.path.relpath(x, 'tests/test_dbs/'))
    mocker.patch.object(
        utils.db_handling.db_access, 'get_database_info_df', return_value=test_df, autospec=True)
    mocker.patch.object(utils.db_handling.db_access, 'Databases', autospec=True)
    return utils.db_handling.db_access.DbAccessor(db_base_path)


@pytest.mark.parametrize("yaml,ver,db_type,scope,exists", testdata)
def test_return_of_db_version(yaml, ver, db_type, scope, exists):
    if exists:
        db_obj = Database(yaml)
        assert db_obj.get_version() == ver


@pytest.mark.parametrize("yaml,ver,db_type,scope,exists", testdata)
def test_return_of_db_type(yaml, ver, db_type, scope, exists):
    if exists:
        db = Database(yaml)
        assert db.get_type() == db_type


@pytest.mark.parametrize("yaml,ver,db_type,scope,exists", testdata)
def test_return_of_scope(yaml, ver, db_type, scope, exists):
    if exists:
        db = Database(yaml)
        assert db.get_scope() == scope


def test_fail_of_empty_database_yaml(mocker):
    mocker.patch.object(utils.databases.superclasses.database_superclasses, 'load_yaml', return_value={})
    from utils.exceptions import NotAValidDatabaseConfigError
    with pytest.raises(NotAValidDatabaseConfigError):
        Database("path_does_not_matter")


# deactivated due to interconnectivity with Databases class. Dunno how to solve this atm
# @pytest.mark.parametrize("yaml,ver,db_type,scope,exists", testdata)
# def test_db_accessor(db_accessor, yaml, ver, db_type, scope, exists):
#     assert db_accessor.get_database_yaml(db_type, ver) == yaml


class TestDbAccessor:
    db_accessor_test_df = testdata_df[['db_type', 'scope', 'db_version', 'db_yaml']].copy()

    def test__get_database_info_df(self, mocker, db_base_path="testing", test_df=db_accessor_test_df.copy()):
        test_df['db_yaml'] = test_df['db_yaml'].apply(lambda x: os.path.relpath(os.path.abspath(x), db_base_path))
        mocker.patch.object(
            utils.db_handling.db_access, 'get_database_info_df', return_value=test_df.copy(), autospec=True)
        mocker.patch.object(utils.db_handling.db_access, 'Databases', autospec=True)
        DbAccessor(db_base_path)

    def test_print_summary(self, db_accessor):
        db_accessor.print_summary()

    @pytest.mark.parametrize("yaml,ver,db_type,scope,exists", testdata)
    def test_is_db_obj_in_df(self, db_accessor, yaml, ver, db_type, scope, exists):
        assert db_accessor._is_db_obj_in_df(db_type, ver) is exists

    def test_get_impossible_db(self):
        db_accessor = DbAccessor("")
        from utils.exceptions import DatabaseAccessorKeyError
        with pytest.raises(DatabaseAccessorKeyError):
            db_accessor.get_database_yaml('wasd')

    def test_create_db_object_for_non_existant_downloadable_db(self):
        db_accessor = DbAccessor("")
        db_accessor.get_database_object('pfams', '32')

    def test_get_downloadable_dbs(self):
        DbAccessor.get_downloadable_databases()


class TestDbObjCreator:
    def test_create_from_existing_files(self):
        db_yaml = 'tests/test_dbs/tigrfams/15.0/db_config.yaml'
        creator = DbObjBuilder.build_with_existing_requirements(db_yaml)
        db_from_files = creator.create()

    def test_create_without_existing_files(self):
        db_yaml = 'tests/test_dbs/tigrfams/15.0/db_config.yaml'
        _, _, db_name, db_version, _ = db_yaml.split('/')
        creator = DbObjBuilder(db_name, db_version, 'tests/test_dbs/')
        db = creator.create()

    def test_db_with_requirements(self):
        db_name = 'ncbi_nr'
        db_version = datetime.datetime.now().strftime("%Y-%m-%d")
        creator = DbObjBuilder(db_name, db_version, 'tests/test_dbs/', check_required_file_presence=False,
                               build_required_db_objects=True)
        db = creator.create()


def test_database_yaml_writer_fails_for_existing_file(tmpdir):
    db_yaml = 'tests/test_dbs/ncbi_nr/2018-08-08/db_config.yaml'
    db = Database(db_yaml)
    import os
    new_file = os.path.join(tmpdir, 'out.yaml')
    from pathlib import Path
    Path(new_file).touch()
    db.yaml = new_file
    with pytest.raises(FileExistsError):
        db.write_yaml()


def test_database_yaml_writer_force_for_existing_file(tmpdir):
    db_yaml = 'tests/test_dbs/ncbi_nr/2018-08-08/db_config.yaml'
    db = Database(db_yaml)
    import os
    new_file = os.path.join(tmpdir, 'out.yaml')
    from pathlib import Path
    Path(new_file).touch()
    db.yaml = new_file
    db.write_yaml(force=True)
