# Copyright © 2020 Interplanetary Database Association e.V.,
# Planetmint and IPDB software contributors.
# SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
# Code is Apache-2.0 and docs are CC-BY-4.0

import copy
from unittest.mock import mock_open, patch

import pytest

import planetmint


ORIGINAL_CONFIG = copy.deepcopy(planetmint._config)


@pytest.fixture(scope='function', autouse=True)
def clean_config(monkeypatch, request):
    original_config = copy.deepcopy(ORIGINAL_CONFIG)
    backend = request.config.getoption('--database-backend')
    original_config['database'] = planetmint._database_map[backend]
    monkeypatch.setattr('planetmint.config', original_config)


def test_bigchain_instance_is_initialized_when_conf_provided():
    import planetmint
    from planetmint import config_utils
    assert 'CONFIGURED' not in planetmint.config

    config_utils.set_config({'database': {'backend': 'a'}})

    assert planetmint.config['CONFIGURED'] is True


def test_load_validation_plugin_loads_default_rules_without_name():
    from planetmint import config_utils
    from planetmint.validation import BaseValidationRules

    assert config_utils.load_validation_plugin() == BaseValidationRules


def test_load_validation_plugin_raises_with_unknown_name():
    from pkg_resources import ResolutionError
    from planetmint import config_utils

    with pytest.raises(ResolutionError):
        config_utils.load_validation_plugin('bogus')


def test_load_validation_plugin_raises_with_invalid_subclass(monkeypatch):
    # Monkeypatch entry_point.load to return something other than a
    # ValidationRules instance
    from planetmint import config_utils
    import time
    monkeypatch.setattr(config_utils,
                        'iter_entry_points',
                        lambda *args: [type('entry_point', (object, ), {'load': lambda: object})])

    with pytest.raises(TypeError):
        # Since the function is decorated with `lru_cache`, we need to
        # "miss" the cache using a name that has not been used previously
        config_utils.load_validation_plugin(str(time.time()))


def test_load_events_plugins(monkeypatch):
    from planetmint import config_utils
    monkeypatch.setattr(config_utils,
                        'iter_entry_points',
                        lambda *args: [type('entry_point', (object, ), {'load': lambda: object})])

    plugins = config_utils.load_events_plugins(['one', 'two'])
    assert len(plugins) == 2


def test_map_leafs_iterator():
    from planetmint import config_utils

    mapping = {
        'a': {'b': {'c': 1},
              'd': {'z': 44}},
        'b': {'d': 2},
        'c': 3
    }

    result = config_utils.map_leafs(lambda x, path: x * 2, mapping)
    assert result == {
        'a': {'b': {'c': 2},
              'd': {'z': 88}},
        'b': {'d': 4},
        'c': 6
    }

    result = config_utils.map_leafs(lambda x, path: path, mapping)
    assert result == {
        'a': {'b': {'c': ['a', 'b', 'c']},
              'd': {'z': ['a', 'd', 'z']}},
        'b': {'d': ['b', 'd']},
        'c': ['c']
    }


def test_update_types():
    from planetmint import config_utils

    raw = {
        'a_string': 'test',
        'an_int': '42',
        'a_float': '3.14',
        'a_list': 'a:b:c',
    }

    reference = {
        'a_string': 'test',
        'an_int': 42,
        'a_float': 3.14,
        'a_list': ['a', 'b', 'c'],
    }

    result = config_utils.update_types(raw, reference)
    assert result == reference


def test_env_config(monkeypatch):
    monkeypatch.setattr('os.environ', {'PLANETMINT_DATABASE_HOST': 'test-host',
                                       'PLANETMINT_DATABASE_PORT': 'test-port'})

    from planetmint import config_utils

    result = config_utils.env_config({'database': {'host': None, 'port': None}})
    expected = {'database': {'host': 'test-host', 'port': 'test-port'}}

    assert result == expected


def test_autoconfigure_read_both_from_file_and_env(monkeypatch, request):
    # constants
    DATABASE_HOST = 'test-host'
    DATABASE_NAME = 'test-dbname'
    DATABASE_PORT = 4242
    DATABASE_BACKEND = request.config.getoption('--database-backend')
    SERVER_BIND = '1.2.3.4:56'
    WSSERVER_SCHEME = 'ws'
    WSSERVER_HOST = '1.2.3.4'
    WSSERVER_PORT = 57
    WSSERVER_ADVERTISED_SCHEME = 'wss'
    WSSERVER_ADVERTISED_HOST = 'a.b.c.d'
    WSSERVER_ADVERTISED_PORT = 89
    LOG_FILE = '/somewhere/something.log'

    file_config = {
        'database': {
            'host': DATABASE_HOST
        },
        'log': {
            'level_console': 'debug',
        },
    }

    monkeypatch.setattr('planetmint.config_utils.file_config',
                        lambda *args, **kwargs: file_config)

    monkeypatch.setattr('os.environ', {
        'PLANETMINT_DATABASE_NAME': DATABASE_NAME,
        'PLANETMINT_DATABASE_PORT': str(DATABASE_PORT),
        'PLANETMINT_DATABASE_BACKEND': DATABASE_BACKEND,
        'PLANETMINT_SERVER_BIND': SERVER_BIND,
        'PLANETMINT_WSSERVER_SCHEME': WSSERVER_SCHEME,
        'PLANETMINT_WSSERVER_HOST': WSSERVER_HOST,
        'PLANETMINT_WSSERVER_PORT': WSSERVER_PORT,
        'PLANETMINT_WSSERVER_ADVERTISED_SCHEME': WSSERVER_ADVERTISED_SCHEME,
        'PLANETMINT_WSSERVER_ADVERTISED_HOST': WSSERVER_ADVERTISED_HOST,
        'PLANETMINT_WSSERVER_ADVERTISED_PORT': WSSERVER_ADVERTISED_PORT,
        'PLANETMINT_LOG_FILE': LOG_FILE,
        'PLANETMINT_LOG_FILE': LOG_FILE,
        'PLANETMINT_DATABASE_CA_CERT': 'ca_cert',
        'PLANETMINT_DATABASE_CRLFILE': 'crlfile',
        'PLANETMINT_DATABASE_CERTFILE': 'certfile',
        'PLANETMINT_DATABASE_KEYFILE': 'keyfile',
        'PLANETMINT_DATABASE_KEYFILE_PASSPHRASE': 'passphrase',
    })

    import planetmint
    from planetmint import config_utils
    from planetmint.log import DEFAULT_LOGGING_CONFIG as log_config
    config_utils.autoconfigure()

    database_mongodb = {
        'backend': 'localmongodb',
        'host': DATABASE_HOST,
        'port': DATABASE_PORT,
        'name': DATABASE_NAME,
        'connection_timeout': 5000,
        'max_tries': 3,
        'replicaset': None,
        'ssl': False,
        'login': None,
        'password': None,
        'ca_cert': 'ca_cert',
        'certfile': 'certfile',
        'keyfile': 'keyfile',
        'keyfile_passphrase': 'passphrase',
        'crlfile': 'crlfile',
    }

    assert planetmint.config == {
        'CONFIGURED': True,
        'server': {
            'bind': SERVER_BIND,
            'loglevel': 'info',
            'workers': None,
        },
        'wsserver': {
            'scheme': WSSERVER_SCHEME,
            'host': WSSERVER_HOST,
            'port': WSSERVER_PORT,
            'advertised_scheme': WSSERVER_ADVERTISED_SCHEME,
            'advertised_host': WSSERVER_ADVERTISED_HOST,
            'advertised_port': WSSERVER_ADVERTISED_PORT,
        },
        'database': database_mongodb,
        'tendermint': {
            'host': 'localhost',
            'port': 26657,
            'version': 'v0.34.15'
        },
        'log': {
            'file': LOG_FILE,
            'level_console': 'debug',
            'error_file': log_config['handlers']['errors']['filename'],
            'level_console': 'debug',
            'level_logfile': 'info',
            'datefmt_console': log_config['formatters']['console']['datefmt'],
            'datefmt_logfile': log_config['formatters']['file']['datefmt'],
            'fmt_console': log_config['formatters']['console']['format'],
            'fmt_logfile': log_config['formatters']['file']['format'],
            'granular_levels': {},
        },
    }


def test_autoconfigure_env_precedence(monkeypatch):
    file_config = {
        'database': {'host': 'test-host', 'name': 'planetmint', 'port': 28015}
    }
    monkeypatch.setattr('planetmint.config_utils.file_config', lambda *args, **kwargs: file_config)
    monkeypatch.setattr('os.environ', {'PLANETMINT_DATABASE_NAME': 'test-dbname',
                                       'PLANETMINT_DATABASE_PORT': '4242',
                                       'PLANETMINT_SERVER_BIND': 'localhost:9985'})

    import planetmint
    from planetmint import config_utils
    config_utils.autoconfigure()

    assert planetmint.config['CONFIGURED']
    assert planetmint.config['database']['host'] == 'test-host'
    assert planetmint.config['database']['name'] == 'test-dbname'
    assert planetmint.config['database']['port'] == 4242
    assert planetmint.config['server']['bind'] == 'localhost:9985'


def test_autoconfigure_explicit_file(monkeypatch):
    from planetmint import config_utils

    def file_config(*args, **kwargs):
        raise FileNotFoundError()

    monkeypatch.setattr('planetmint.config_utils.file_config', file_config)

    with pytest.raises(FileNotFoundError):
        config_utils.autoconfigure(filename='autoexec.bat')


def test_update_config(monkeypatch):
    import planetmint
    from planetmint import config_utils

    file_config = {
        'database': {'host': 'test-host', 'name': 'planetmint', 'port': 28015}
    }
    monkeypatch.setattr('planetmint.config_utils.file_config', lambda *args, **kwargs: file_config)
    config_utils.autoconfigure(config=file_config)

    # update configuration, retaining previous changes
    config_utils.update_config({'database': {'port': 28016, 'name': 'planetmint_other'}})

    assert planetmint.config['database']['host'] == 'test-host'
    assert planetmint.config['database']['name'] == 'planetmint_other'
    assert planetmint.config['database']['port'] == 28016


def test_file_config():
    from planetmint.config_utils import file_config, CONFIG_DEFAULT_PATH
    with patch('builtins.open', mock_open(read_data='{}')) as m:
        config = file_config()
    m.assert_called_once_with(CONFIG_DEFAULT_PATH)
    assert config == {}


def test_invalid_file_config():
    from planetmint.config_utils import file_config
    from planetmint.transactions.common import exceptions
    with patch('builtins.open', mock_open(read_data='{_INVALID_JSON_}')):
        with pytest.raises(exceptions.ConfigurationError):
            file_config()


def test_write_config():
    from planetmint.config_utils import write_config, CONFIG_DEFAULT_PATH
    m = mock_open()
    with patch('builtins.open', m):
        write_config({})
    m.assert_called_once_with(CONFIG_DEFAULT_PATH, 'w')
    handle = m()
    handle.write.assert_called_once_with('{}')


@pytest.mark.parametrize('env_name,env_value,config_key', (
    ('PLANETMINT_DATABASE_BACKEND', 'test-backend', 'backend'),
    ('PLANETMINT_DATABASE_HOST', 'test-host', 'host'),
    ('PLANETMINT_DATABASE_PORT', 4242, 'port'),
    ('PLANETMINT_DATABASE_NAME', 'test-db', 'name'),
))
def test_database_envs(env_name, env_value, config_key, monkeypatch):
    import planetmint

    monkeypatch.setattr('os.environ', {env_name: env_value})
    planetmint.config_utils.autoconfigure()

    expected_config = copy.deepcopy(planetmint.config)
    expected_config['database'][config_key] = env_value

    assert planetmint.config == expected_config
