import dagster as dg
import pytest
from dagster._check import CheckError, ParameterCheckError
from dagster._config import ConfigType, ConfigTypeKind, post_process_config, resolve_to_config_type


def test_post_process_config():
    scalar_config_type = resolve_to_config_type(dg.String)
    assert post_process_config(scalar_config_type, "foo").value == "foo"
    assert post_process_config(scalar_config_type, 3).value == 3
    assert post_process_config(scalar_config_type, {}).value == {}
    assert post_process_config(scalar_config_type, None).value is None

    enum_config_type = resolve_to_config_type(
        dg.Enum("an_enum", [dg.EnumValue("foo"), dg.EnumValue("bar", python_value=3)])
    )
    assert post_process_config(enum_config_type, "foo").value == "foo"
    assert post_process_config(enum_config_type, "bar").value == 3
    with pytest.raises(CheckError):
        post_process_config(enum_config_type, "baz")
    with pytest.raises(CheckError):
        post_process_config(enum_config_type, None)

    list_config_type = resolve_to_config_type([str])
    assert post_process_config(list_config_type, ["foo"]).value == ["foo"]
    assert post_process_config(list_config_type, None).value == []
    with pytest.raises(CheckError, match="Null array member not caught"):
        assert post_process_config(list_config_type, [None]).value == [None]

    nullable_list_config_type = resolve_to_config_type([dg.Noneable(str)])
    assert post_process_config(nullable_list_config_type, ["foo"]).value == ["foo"]
    assert post_process_config(nullable_list_config_type, [None]).value == [None]
    assert post_process_config(nullable_list_config_type, None).value == []

    map_config_type = resolve_to_config_type({str: int})
    assert post_process_config(map_config_type, {"foo": 5}).value == {"foo": 5}  # pyright: ignore[reportArgumentType]
    assert post_process_config(map_config_type, None).value == {}  # pyright: ignore[reportArgumentType]
    with pytest.raises(CheckError, match="Null map member not caught"):
        assert post_process_config(map_config_type, {"foo": None}).value == {"foo": None}  # pyright: ignore[reportArgumentType]

    nullable_map_config_type = resolve_to_config_type({str: dg.Noneable(int)})
    assert post_process_config(nullable_map_config_type, {"foo": 5}).value == {"foo": 5}  # pyright: ignore[reportArgumentType]
    assert post_process_config(nullable_map_config_type, {"foo": None}).value == {"foo": None}  # pyright: ignore[reportArgumentType]
    assert post_process_config(nullable_map_config_type, None).value == {}  # pyright: ignore[reportArgumentType]

    composite_config_type = resolve_to_config_type(
        {
            "foo": dg.String,
            "bar": {"baz": [str]},
            "quux": dg.Field(str, is_required=False, default_value="zip"),
            "quiggle": dg.Field(str, is_required=False),
            "werty": dg.Field({str: [int]}, is_required=False),
        }
    )
    with pytest.raises(CheckError, match="Missing required composite member"):
        post_process_config(composite_config_type, {})

    with pytest.raises(CheckError, match="Missing required composite member"):
        post_process_config(composite_config_type, {"bar": {"baz": ["giraffe"]}, "quux": "nimble"})

    with pytest.raises(CheckError, match="Missing required composite member"):
        post_process_config(composite_config_type, {"foo": "zowie", "quux": "nimble"})

    assert post_process_config(
        composite_config_type, {"foo": "zowie", "bar": {"baz": ["giraffe"]}, "quux": "nimble"}
    ).value == {"foo": "zowie", "bar": {"baz": ["giraffe"]}, "quux": "nimble"}

    assert post_process_config(
        composite_config_type, {"foo": "zowie", "bar": {"baz": ["giraffe"]}}
    ).value == {"foo": "zowie", "bar": {"baz": ["giraffe"]}, "quux": "zip"}

    assert post_process_config(
        composite_config_type, {"foo": "zowie", "bar": {"baz": ["giraffe"]}, "quiggle": "squiggle"}
    ).value == {"foo": "zowie", "bar": {"baz": ["giraffe"]}, "quux": "zip", "quiggle": "squiggle"}

    assert post_process_config(
        composite_config_type,
        {
            "foo": "zowie",
            "bar": {"baz": ["giraffe"]},
            "quiggle": "squiggle",
            "werty": {"asdf": [1, 2, 3]},
        },
    ).value == {
        "foo": "zowie",
        "bar": {"baz": ["giraffe"]},
        "quux": "zip",
        "quiggle": "squiggle",
        "werty": {"asdf": [1, 2, 3]},
    }

    nested_composite_config_type = resolve_to_config_type(
        {
            "fruts": {
                "apple": dg.Field(dg.String),
                "banana": dg.Field(dg.String, is_required=False),
                "potato": dg.Field(dg.String, is_required=False, default_value="pie"),
            }
        }
    )

    with pytest.raises(CheckError, match="Missing required composite member"):
        post_process_config(nested_composite_config_type, {"fruts": None})

    with pytest.raises(CheckError, match="Missing required composite member"):
        post_process_config(
            nested_composite_config_type, {"fruts": {"banana": "good", "potato": "bad"}}
        )

    assert post_process_config(
        nested_composite_config_type, {"fruts": {"apple": "strawberry"}}
    ).value == {"fruts": {"apple": "strawberry", "potato": "pie"}}

    assert post_process_config(
        nested_composite_config_type, {"fruts": {"apple": "a", "banana": "b", "potato": "c"}}
    ).value == {"fruts": {"apple": "a", "banana": "b", "potato": "c"}}

    any_config_type = resolve_to_config_type(dg.Any)

    assert post_process_config(any_config_type, {"foo": "bar"}).value == {"foo": "bar"}  # pyright: ignore[reportArgumentType]

    assert post_process_config(
        ConfigType("gargle", given_name="bargle", kind=ConfigTypeKind.ANY), 3
    )

    selector_config_type = resolve_to_config_type(
        dg.Selector(
            {
                "one": dg.Field(dg.String),
                "another": {"foo": dg.Field(dg.String, default_value="bar", is_required=False)},
                "yet_another": dg.Field(dg.String, default_value="quux", is_required=False),
            }
        )
    )

    with pytest.raises(CheckError):
        post_process_config(selector_config_type, "one")

    with pytest.raises(ParameterCheckError):
        post_process_config(selector_config_type, None)

    with pytest.raises(ParameterCheckError, match="Expected dict with single item"):
        post_process_config(selector_config_type, {})

    with pytest.raises(CheckError):
        post_process_config(selector_config_type, {"one": "foo", "another": "bar"})

    assert post_process_config(selector_config_type, {"one": "foo"}).value == {"one": "foo"}

    assert post_process_config(selector_config_type, {"one": None}).value == {"one": None}

    assert post_process_config(selector_config_type, {"one": {}}).value == {"one": {}}

    assert post_process_config(selector_config_type, {"another": {}}).value == {
        "another": {"foo": "bar"}
    }

    singleton_selector_config_type = resolve_to_config_type(
        dg.Selector({"foo": dg.Field(dg.String, default_value="bar", is_required=False)})
    )

    assert post_process_config(singleton_selector_config_type, None).value == {"foo": "bar"}

    permissive_dict_config_type = resolve_to_config_type(
        dg.Permissive(
            {
                "foo": dg.Field(dg.String),
                "bar": dg.Field(dg.String, default_value="baz", is_required=False),
            }
        )
    )

    with pytest.raises(CheckError, match="Missing required composite member"):
        post_process_config(permissive_dict_config_type, None)

    assert post_process_config(permissive_dict_config_type, {"foo": "wow", "mau": "mau"}).value == {
        "foo": "wow",
        "bar": "baz",
        "mau": "mau",
    }

    noneable_permissive_config_type = resolve_to_config_type(
        {"args": dg.Field(dg.Noneable(dg.Permissive()), is_required=False, default_value=None)}
    )
    assert post_process_config(  # pyright: ignore[reportOptionalSubscript]
        noneable_permissive_config_type,
        {"args": {"foo": "wow", "mau": "mau"}},
    ).value["args"] == {
        "foo": "wow",
        "mau": "mau",
    }
    assert post_process_config(noneable_permissive_config_type, {"args": {}}).value["args"] == {}  # pyright: ignore[reportOptionalSubscript]
    assert post_process_config(noneable_permissive_config_type, None).value["args"] is None  # pyright: ignore[reportOptionalSubscript]
