"""Container provider tests."""

import copy

from dependency_injector import containers, providers, errors
from pytest import raises


TEST_VALUE_1 = "core_section_value1"
TEST_CONFIG_1 = {
    "core": {
        "section": {
            "value": TEST_VALUE_1,
        },
    },
}

TEST_VALUE_2 = "core_section_value2"
TEST_CONFIG_2 = {
    "core": {
        "section": {
            "value": TEST_VALUE_2,
        },
    },
}


def _copied(value):
    return copy.deepcopy(value)


class Core(containers.DeclarativeContainer):
    config = providers.Configuration("core")
    value_getter = providers.Callable(lambda _: _, config.section.value)


class Application(containers.DeclarativeContainer):
    config = providers.Configuration("config")
    core = providers.Container(Core, config=config.core)
    dict_factory = providers.Factory(dict, value=core.value_getter)


def test():
    application = Application(config=_copied(TEST_CONFIG_1))
    assert application.dict_factory() == {"value": TEST_VALUE_1}


def test_double_override():
    application = Application()
    application.config.override(_copied(TEST_CONFIG_1))
    application.config.override(_copied(TEST_CONFIG_2))
    assert application.dict_factory() == {"value": TEST_VALUE_2}


def test_override():
    # See: https://github.com/ets-labs/python-dependency-injector/issues/354
    class D(containers.DeclarativeContainer):
        foo = providers.Object("foo")

    class A(containers.DeclarativeContainer):
        d = providers.DependenciesContainer()
        bar = providers.Callable(lambda f: f + "++", d.foo.provided)

    class B(containers.DeclarativeContainer):
        d = providers.Container(D)

        a = providers.Container(A, d=d)

    b = B(d=D())
    result = b.a().bar()
    assert result == "foo++"


def test_override_not_root_provider():
    # See: https://github.com/ets-labs/python-dependency-injector/issues/379
    class NestedContainer(containers.DeclarativeContainer):
        settings = providers.Configuration()

        print_settings = providers.Callable(
            lambda s: s,
            settings,
        )

    class TestContainer(containers.DeclarativeContainer):
        settings = providers.Configuration()

        root_container = providers.Container(
            NestedContainer,
            settings=settings,
        )

        not_root_container = providers.Selector(
            settings.container,
            using_factory=providers.Factory(
                NestedContainer,
                settings=settings,
            ),
            using_container=providers.Container(
                NestedContainer,
                settings=settings,
            )
        )

    container_using_factory = TestContainer(settings=dict(
        container="using_factory",
        foo="bar"
    ))
    assert container_using_factory.root_container().print_settings() == {"container": "using_factory", "foo": "bar"}
    assert container_using_factory.not_root_container().print_settings() == {"container": "using_factory", "foo": "bar"}

    container_using_container = TestContainer(settings=dict(
        container="using_container",
        foo="bar"
    ))
    assert container_using_container.root_container().print_settings() == {"container": "using_container", "foo": "bar"}
    assert container_using_container.not_root_container().print_settings() == {"container": "using_container", "foo": "bar"}


def test_override_by_not_a_container():
    provider = providers.Container(Core)

    with raises(errors.Error):
        provider.override(providers.Object("foo"))


def test_lazy_overriding():
    # See: https://github.com/ets-labs/python-dependency-injector/issues/354
    class D(containers.DeclarativeContainer):
        foo = providers.Object("foo")

    class A(containers.DeclarativeContainer):
        d = providers.DependenciesContainer()
        bar = providers.Callable(lambda f: f + "++", d.foo.provided)

    class B(containers.DeclarativeContainer):
        d = providers.DependenciesContainer()

        a = providers.Container(A, d=d)

    b = B(d=D())
    result = b.a().bar()
    assert result == "foo++"


def test_lazy_overriding_deep():
    # Extended version of test_lazy_overriding()
    class D(containers.DeclarativeContainer):
        foo = providers.Object("foo")

    class C(containers.DeclarativeContainer):
        d = providers.DependenciesContainer()
        bar = providers.Callable(lambda f: f + "++", d.foo.provided)

    class A(containers.DeclarativeContainer):
        d = providers.DependenciesContainer()
        c = providers.Container(C, d=d)

    class B(containers.DeclarativeContainer):
        d = providers.DependenciesContainer()

        a = providers.Container(A, d=d)

    b = B(d=D())
    result = b.a().c().bar()
    assert result == "foo++"


def test_reset_last_overriding():
    application = Application(config=_copied(TEST_CONFIG_1))
    application.core.override(Core(config=_copied(TEST_CONFIG_2["core"])))

    application.core.reset_last_overriding()

    assert application.dict_factory() == {"value": TEST_VALUE_1}


def test_reset_last_overriding_only_overridden():
    application = Application(config=_copied(TEST_CONFIG_1))
    application.core.override(providers.DependenciesContainer(config=_copied(TEST_CONFIG_2["core"])))

    application.core.reset_last_overriding()

    assert application.dict_factory() == {"value": TEST_VALUE_1}


def test_override_context_manager():
    application = Application(config=_copied(TEST_CONFIG_1))
    overriding_core = Core(config=_copied(TEST_CONFIG_2["core"]))

    with application.core.override(overriding_core) as context_core:
        assert application.dict_factory() == {"value": TEST_VALUE_2}
        assert context_core() is overriding_core

    assert application.dict_factory() == {"value": TEST_VALUE_1}


def test_reset_override():
    application = Application(config=_copied(TEST_CONFIG_1))
    application.core.override(Core(config=_copied(TEST_CONFIG_2["core"])))

    application.core.reset_override()

    assert application.dict_factory() == {"value": None}


def test_reset_override_only_overridden():
    application = Application(config=_copied(TEST_CONFIG_1))
    application.core.override(providers.DependenciesContainer(config=_copied(TEST_CONFIG_2["core"])))

    application.core.reset_override()

    assert application.dict_factory() == {"value": None}


def test_assign_parent():
    parent = providers.DependenciesContainer()
    provider = providers.Container(Core)

    provider.assign_parent(parent)

    assert provider.parent is parent


def test_parent_name():
    container = containers.DynamicContainer()
    provider = providers.Container(Core)
    container.name = provider
    assert provider.parent_name == "name"


def test_parent_name_with_deep_parenting():
    provider = providers.Container(Core)
    container = providers.DependenciesContainer(name=provider)
    _ = providers.DependenciesContainer(container=container)
    assert provider.parent_name == "container.name"


def test_parent_name_is_none():
    provider = providers.Container(Core)
    assert provider.parent_name is None


def test_parent_deepcopy():
    container = containers.DynamicContainer()
    provider = providers.Container(Core)
    container.name = provider

    copied = providers.deepcopy(container)

    assert container.name.parent is container
    assert copied.name.parent is copied

    assert container is not copied
    assert container.name is not copied.name
    assert container.name.parent is not copied.name.parent


def test_resolve_provider_name():
    container = providers.Container(Core)
    assert container.resolve_provider_name(container.value_getter) == "value_getter"


def test_resolve_provider_name_no_provider():
    container = providers.Container(Core)
    with raises(errors.Error):
        container.resolve_provider_name(providers.Provider())