# 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 pytest
import planetmint

from unittest.mock import mock_open, patch
from planetmint.config import Config


@pytest.fixture(scope="function", autouse=True)
def clean_config(monkeypatch, request):
    original_config = Config().init_config("tarantool_db")
    backend = request.config.getoption("--database-backend")
    original_config["database"] = Config().get_db_map(backend)
    monkeypatch.setattr("planetmint.config", original_config)


def test_bigchain_instance_is_initialized_when_conf_provided():
    from planetmint import config_utils

    assert "CONFIGURED" not in Config().get()

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

    assert Config().get()["CONFIGURED"] is True


def test_load_validation_plugin_loads_default_rules_without_name():
    from planetmint import config_utils
    from planetmint.application.basevalidationrules 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


@pytest.mark.skip(reason="Disabled until we create a better config format")
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",
        },
    )

    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.24"},
        "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",
        },
    )
    from planetmint import config_utils
    from planetmint.config import Config

    config_utils.autoconfigure()

    assert Config().get()["CONFIGURED"]
    assert Config().get()["database"]["host"] == "test-host"
    assert Config().get()["database"]["name"] == "test-dbname"
    assert Config().get()["database"]["port"] == 4242
    assert Config().get()["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):
    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 Config().get()["database"]["host"] == "test-host"
    assert Config().get()["database"]["name"] == "planetmint_other"
    assert Config().get()["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 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):
    monkeypatch.setattr("os.environ", {env_name: env_value})
    planetmint.config_utils.autoconfigure()

    expected_config = Config().get()
    expected_config["database"][config_key] = env_value

    assert planetmint.config.Config().get() == expected_config