from typing import Any, Dict, List

from pytest import mark, param, raises

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


@mark.parametrize(
    ("cfg", "key", "expected"),
    [
        # Note that since `oc.create` is simply calling `OmegaConf.create`, which is
        # already thoroughly tested, we do not do extensive tests here.
        param(
            {"x": "${oc.create:{a: 0, b: 1}}"},
            "x",
            OmegaConf.create({"a": 0, "b": 1}),
            id="dict",
        ),
        param(
            {"x": "${oc.create:[0, 1, 2]}"},
            "x",
            OmegaConf.create([0, 1, 2]),
            id="list",
        ),
        param(
            {"x": "${oc.create:{a: 0, b: ${y}}}", "y": 5},
            "x",
            OmegaConf.create({"a": 0, "b": 5}),
            id="dict:interpolated_value",
        ),
        param(
            {"x": "${oc.create:[0, 1, ${y}]}", "y": 5},
            "x",
            OmegaConf.create([0, 1, 5]),
            id="list:interpolated_value",
        ),
        param(
            {"x": "${oc.create:${y}}", "y": {"a": 0}},
            "x",
            OmegaConf.create({"a": 0}),
            id="dict:interpolated_node",
        ),
        param(
            {"x": "${oc.create:${y}}", "y": [0, 1]},
            "x",
            OmegaConf.create([0, 1]),
            id="list:interpolated_node",
        ),
    ],
)
def test_create(cfg: Any, key: str, expected: Any) -> None:
    cfg = OmegaConf.create(cfg)
    val = cfg[key]
    assert val == expected
    assert type(val) is type(expected)
    assert val._get_flag("readonly") is None


def test_create_error() -> None:
    cfg = OmegaConf.create({"x": "${oc.create:0}"})
    with raises(InterpolationResolutionError, match="ValidationError"):
        cfg.x


def test_write_into_output() -> None:
    cfg = OmegaConf.create(
        {
            "x": "${oc.create:${y}}",
            "y": {
                "a": 0,
                "b": {"c": 1},
            },
        }
    )
    x = cfg.x
    assert x._get_flag("readonly") is None

    # Write into the node generated by `oc.create`.
    x.a = 1
    x.b.c = 2

    # The node that we wrote into should be modified.
    assert x.a == 1
    assert x.b.c == 2

    # The interpolated node should not be modified.
    assert cfg.y.a == 0
    assert cfg.y.b.c == 1

    # Re-accessing the node "forgets" the changes.
    assert cfg.x.a == 0
    assert cfg.x.b.c == 1


@mark.parametrize(
    ("cfg", "expected"),
    [
        ({"a": 0, "b": 1}, {"a": 0, "b": 1}),
        ({"a": "${y}"}, {"a": -1}),
        ({"a": 0, "b": "${x.a}"}, {"a": 0, "b": 0}),
        ({"a": 0, "b": "${.a}"}, {"a": 0, "b": 0}),
        ({"a": "${..y}"}, {"a": -1}),
    ],
)
def test_resolver_output_dict_to_dictconfig(
    restore_resolvers: Any, cfg: Dict[str, Any], expected: Dict[str, Any]
) -> None:
    OmegaConf.register_new_resolver("dict", lambda: cfg)
    c = OmegaConf.create({"x": "${oc.create:${dict:}}", "y": -1})
    assert isinstance(c.x, DictConfig)
    assert c.x == expected
    assert c.x._parent is c


@mark.parametrize(
    ("cfg", "expected"),
    [
        ([0, 1], [0, 1]),
        (["${y}"], [-1]),
        ([0, "${x.0}"], [0, 0]),
        ([0, "${.0}"], [0, 0]),
        (["${..y}"], [-1]),
    ],
)
def test_resolver_output_list_to_listconfig(
    restore_resolvers: Any, cfg: List[Any], expected: List[Any]
) -> None:
    OmegaConf.register_new_resolver("list", lambda: cfg)
    c = OmegaConf.create({"x": "${oc.create:${list:}}", "y": -1})
    assert isinstance(c.x, ListConfig)
    assert c.x == expected
    assert c.x._parent is c


def test_merge_into_created_node() -> None:
    cfg: Any = OmegaConf.create({"x": "${oc.create:{y: 0}}"})
    cfg = OmegaConf.merge(cfg, {"x": {"z": 1}})
    assert cfg == {"x": {"y": 0, "z": 1}}
