"""Testing for OmegaConf"""
import re
import sys
from typing import Any, Dict, List, Optional

import pytest
import yaml

from omegaconf import DictConfig, ListConfig, OmegaConf
from omegaconf.errors import UnsupportedValueType

from . import ConcretePlugin, IllegalType, NonCopyableIllegalType, Plugin


@pytest.mark.parametrize(
    "input_,expected",
    [
        # No content
        (None, None),
        # empty
        ({}, {}),
        # simple value
        ("hello", {"hello": None}),
        # simple key:value"
        ("hello: world", {"hello": "world"}),
        ({"hello": {"a": 2}}, {"hello": {"a": 2}}),
        # empty input
        ("", {}),
        # list value
        ([1, 2], [1, 2]),
        # For simplicity, tuples are converted to lists.
        ((1, 2), [1, 2]),
        # dict 1
        ({"a": 2, "b": 10}, {"a": 2, "b": 10}),
        # dict 2
        (dict(a=2, b=10), dict(a=2, b=10)),
        # nested dict
        (
            {"a": 2, "b": {"c": {"f": 1}, "d": {}}},
            {"a": 2, "b": {"c": {"f": 1}, "d": {}}},
        ),
        ({"a": None}, {"a": None}),
        ({"foo": "${missing}"}, {"foo": "${missing}"}),
        (OmegaConf.create({"foo": "${missing}"}), {"foo": "${missing}"}),
        (OmegaConf.create(), {}),
        (OmegaConf.create({}), {}),
        (OmegaConf.create([]), []),
        (OmegaConf.create({"foo": OmegaConf.create([])}), {"foo": []}),
        (OmegaConf.create([OmegaConf.create({})]), [{}]),
    ],
)
def test_create_value(input_: Any, expected: Any) -> None:
    assert OmegaConf.create(input_) == expected


@pytest.mark.parametrize(
    "input_",
    [
        # top level dict
        {"x": IllegalType()},
        {"x": {"y": IllegalType()}},
        {"x": [IllegalType()]},
        # top level list
        [IllegalType()],
        [[IllegalType()]],
        [{"x": IllegalType()}],
        [{"x": [IllegalType()]}],
    ],
)
def test_create_allow_objects(input_: Any) -> None:
    # test creating from a primitive container
    cfg = OmegaConf.create(input_, flags={"allow_objects": True})
    assert cfg == input_

    # test creating from an OmegaConf object, inheriting the allow_objects flag
    cfg = OmegaConf.create(cfg)
    assert cfg == input_

    # test creating from an OmegaConf object
    cfg = OmegaConf.create(cfg, flags={"allow_objects": True})
    assert cfg == input_


@pytest.mark.parametrize(
    "input_",
    [
        # top level dict
        {"x": NonCopyableIllegalType()},
        {"x": {"y": NonCopyableIllegalType()}},
        {"x": [NonCopyableIllegalType()]},
        # top level list
        [NonCopyableIllegalType()],
        [[NonCopyableIllegalType()]],
        [{"x": NonCopyableIllegalType()}],
        [{"x": [NonCopyableIllegalType()]}],
    ],
)
def test_create_allow_objects_non_copyable(input_: Any) -> None:
    # test creating from a primitive container
    cfg = OmegaConf.create(input_, flags={"allow_objects": True})
    assert cfg == input_

    # test creating from an OmegaConf object, inheriting the allow_objects flag
    cfg = OmegaConf.create(cfg)
    assert cfg == input_

    # test creating from an OmegaConf object
    cfg = OmegaConf.create(cfg, flags={"allow_objects": True})
    assert cfg == input_


@pytest.mark.parametrize(
    "input_",
    [
        pytest.param({"foo": "bar"}, id="dict"),
        pytest.param([1, 2, 3], id="list"),
    ],
)
def test_create_flags_overriding(input_: Any) -> Any:
    cfg = OmegaConf.create(input_)
    OmegaConf.set_struct(cfg, True)

    # by default flags are inherited
    cfg2 = OmegaConf.create(cfg)
    assert OmegaConf.is_struct(cfg2)
    assert not OmegaConf.is_readonly(cfg2)

    # but specified flags are replacing all of the flags (even those that are not specified)
    cfg2 = OmegaConf.create(cfg, flags={"readonly": True})
    assert not OmegaConf.is_struct(cfg2)
    assert OmegaConf.is_readonly(cfg2)


def test_create_from_cli() -> None:
    sys.argv = ["program.py", "a=1", "b.c=2"]
    c = OmegaConf.from_cli()
    assert {"a": 1, "b": {"c": 2}} == c


def test_cli_passing() -> None:
    args_list = ["a=1", "b.c=2"]
    c = OmegaConf.from_cli(args_list)
    assert {"a": 1, "b": {"c": 2}} == c


@pytest.mark.parametrize(
    "input_,expected",
    [
        # simple
        (["a=1", "b.c=2"], dict(a=1, b=dict(c=2))),
        # string
        (["a=hello", "b=world"], dict(a="hello", b="world")),
        # date-formatted string
        (["my_date=2019-12-11"], dict(my_date="2019-12-11")),
    ],
)
def test_dotlist(input_: List[str], expected: Dict[str, Any]) -> None:
    c = OmegaConf.from_dotlist(input_)
    assert c == expected


def test_create_list_with_illegal_value_idx0() -> None:
    with pytest.raises(UnsupportedValueType, match=re.escape("key: [0]")):
        OmegaConf.create([IllegalType()])


def test_create_list_with_illegal_value_idx1() -> None:
    lst = [1, IllegalType(), 3]
    with pytest.raises(UnsupportedValueType, match=re.escape("key: [1]")):
        OmegaConf.create(lst)


def test_create_dict_with_illegal_value() -> None:
    with pytest.raises(UnsupportedValueType, match=re.escape("key: a")):
        OmegaConf.create({"a": IllegalType()})


def test_create_nested_dict_with_illegal_value() -> None:
    with pytest.raises(ValueError, match=re.escape("key: a.b")):
        OmegaConf.create({"a": {"b": IllegalType()}})


def test_create_from_oc() -> None:
    c = OmegaConf.create(
        {"a": OmegaConf.create([1, 2, 3]), "b": OmegaConf.create({"c": 10})}
    )
    assert c == {"a": [1, 2, 3], "b": {"c": 10}}


def test_create_from_oc_with_flags() -> None:
    c1 = OmegaConf.create({"foo": "bar"})
    OmegaConf.set_struct(c1, True)
    c2 = OmegaConf.create(c1)
    assert c1 == c2
    assert c1._metadata.flags == c2._metadata.flags


def test_create_from_dictconfig_preserves_metadata() -> None:
    cfg1 = DictConfig(ref_type=Plugin, is_optional=False, content=ConcretePlugin)
    OmegaConf.set_struct(cfg1, True)
    OmegaConf.set_readonly(cfg1, True)
    cfg2 = OmegaConf.create(cfg1)
    assert cfg1 == cfg2
    assert cfg1._metadata == cfg2._metadata


def test_create_from_listconfig_preserves_metadata() -> None:
    cfg1 = ListConfig(element_type=int, is_optional=False, content=[1, 2, 3])
    OmegaConf.set_struct(cfg1, True)
    OmegaConf.set_readonly(cfg1, True)
    cfg2 = OmegaConf.create(cfg1)
    assert cfg1 == cfg2
    assert cfg1._metadata == cfg2._metadata


@pytest.mark.parametrize("node", [({"bar": 10}), ([1, 2, 3])])
def test_create_node_parent_retained_on_create(node: Any) -> None:
    cfg1 = OmegaConf.create({"foo": node})
    cfg2 = OmegaConf.create({"zonk": cfg1.foo})
    assert cfg2 == {"zonk": node}
    assert cfg1.foo._get_parent() == cfg1
    assert cfg1.foo._get_parent() is cfg1


@pytest.mark.parametrize("node", [({"bar": 10}), ([1, 2, 3])])
def test_create_node_parent_retained_on_assign(node: Any) -> None:
    cfg1 = OmegaConf.create({"foo": node})
    cfg2 = OmegaConf.create()
    cfg2.zonk = cfg1.foo
    assert cfg1.foo._get_parent() is cfg1
    assert cfg2.zonk._get_parent() is cfg2


@pytest.mark.parametrize(
    "node",
    [
        {"a": 0},
        DictConfig({"a": 0}),
    ],
)
def test_dict_assignment_deepcopy_semantics(node: Any) -> None:
    cfg = OmegaConf.create()
    cfg.foo = node
    node["a"] = 1
    assert cfg.foo.a == 0


@pytest.mark.parametrize(
    "node",
    [
        [1, 2],
        ListConfig([1, 2]),
    ],
)
def test_list_assignment_deepcopy_semantics(node: Any) -> None:
    cfg = OmegaConf.create()
    cfg.foo = node
    node[1] = 10
    assert cfg.foo[1] == 2


@pytest.mark.parametrize("d", [{"a": {"b": 10}}, {"a": {"b": {"c": 10}}}])
def test_assign_does_not_modify_src_config(d: Any) -> None:
    cfg1 = OmegaConf.create(d)
    cfg2 = OmegaConf.create({})
    cfg2.a = cfg1.a
    assert cfg1 == d
    assert cfg2 == d

    assert cfg1.a._get_parent() is cfg1
    assert cfg1.a._get_node("b")._get_parent() is cfg1.a

    assert cfg2.a._get_parent() is cfg2
    assert cfg2.a._get_node("b")._get_parent() is cfg2.a


def test_create_unmodified_loader() -> None:
    cfg = OmegaConf.create("gitrev: 100e100")
    yaml_cfg = yaml.load("gitrev: 100e100", Loader=yaml.loader.SafeLoader)
    assert cfg.gitrev == 1e102
    assert yaml_cfg["gitrev"] == "100e100"


def test_create_untyped_list() -> None:
    from omegaconf._utils import get_ref_type

    cfg = ListConfig(ref_type=List, content=[])
    assert get_ref_type(cfg) == Optional[List]


def test_create_untyped_dict() -> None:
    from omegaconf._utils import get_ref_type

    cfg = DictConfig(ref_type=Dict, content={})
    assert get_ref_type(cfg) == Optional[Dict]
