import pytest
import re
from typing import Any, Tuple
from dataclasses import dataclass
from app_settings_dict import Settings


def test_simple_settings() -> None:
    settings = Settings(
        settings_file_path="C:/Users/chris/Documents/sample_settings_file_name.json",
        default_factories={
            "key1": lambda: "value1",
        },
        data={
            "key1": "hello",
            "key2": "world",
        },
    )
    assert settings["key1"] == "hello"
    assert settings["key2"] == "world"
    del settings["key1"]
    del settings["key2"]
    assert "key1" not in settings
    assert "key2" not in settings
    assert settings["key1"] == "value1"
    with pytest.raises(KeyError):
        settings["key2"]


def test_default_settings() -> None:
    settings = Settings(
        settings_file_path="sample settings file name.json",
        default_factories={
            "key1": lambda: "value1",
            "key2": lambda: "value2",
            "key3": lambda: "value3",
        },
        default_settings={
            "key3": [],
        },
        data={
            "key1": "hello",
            "key2": "world",
        },
    )
    assert settings["key1"] == "hello"
    assert settings["key2"] == "world"
    assert settings["key3"] == "value3"
    del settings["key3"]
    assert settings["key3"] == "value3"
    settings.reset("key3")
    assert settings["key3"] == []
    settings["key3"] = "something"
    assert settings["key3"] == "something"
    settings.reset_all()
    assert settings["key1"] == "hello"
    assert settings["key2"] == "world"
    assert settings["key3"] == []


def test_load_without_file() -> None:
    def sample_prompt_function(settings: Settings) -> Settings:
        # s = input("Enter the settings: ")
        return settings.update({"key1": "a", "key2": "b"})

    settings = Settings(
        settings_file_path="not a real file.yaml",
        prompt_user_for_all_settings=sample_prompt_function,
        default_factories={
            "key1": lambda: "value1",
            "key2": lambda: "value2",
            "key3": lambda: "value3",
        },
        default_settings={
            "key3": [],
            "key4": "value4",
        },
        data={
            "key1": "hello",
            "key2": "world",
        },
    )
    assert settings["key1"] == "hello"
    assert settings["key2"] == "world"
    assert settings["key3"] == "value3"
    settings.load(fallback_option="prompt user")
    assert settings["key1"] == "a"
    assert settings["key2"] == "b"
    assert settings["key3"] == "value3"
    with pytest.raises(KeyError):
        settings["key4"]
    settings.load(fallback_option="default settings")
    assert settings["key1"] == "a"
    assert settings["key2"] == "b"
    assert settings["key3"] == "value3"
    assert settings["key4"] == "value4"
    settings.clear()
    settings.load(fallback_option="default settings")
    assert settings["key1"] == "hello"
    assert settings["key2"] == "world"
    assert settings["key3"] == []
    assert settings["key4"] == "value4"
    with pytest.raises(ValueError):
        settings.load(fallback_option="invalid option")


def test_load_after_empty() -> None:
    settings = Settings(
        settings_file_path="sample settings file name.json",
        prompt_user_for_all_settings=lambda: 1 / 0,
        default_factories={
            "key1": lambda: "value1",
        },
        default_settings={
            "key1": [],
        },
        data={
            "key1": "hello",
        },
    )
    assert settings["key1"] == "hello"
    settings.clear()
    assert settings["key1"] == "value1"


def test_prompt() -> None:
    def sample_prompt_function() -> Any:
        # s = input("Enter a setting: ")
        return "a"

    settings = Settings(
        settings_file_path="sample settings file name.json",
        prompt_user_for_all_settings=lambda: {"key1": "a", "key2": "b"},
        default_factories={
            "key1": sample_prompt_function,
            "key2": lambda: "value2",
            "key3": lambda: "value3",
        },
        default_settings={
            "key3": [],
        },
        data={
            "key1": "hello",
            "key2": "world",
        },
    )
    assert settings["key1"] == "hello"
    settings.prompt("key1")
    assert settings["key1"] == "a"


def test_changing_settings_before_load() -> None:
    settings = Settings(
        settings_file_path="sample settings file name.json",
        default_factories={
            "key1": lambda: "value1",
        },
        default_settings={
            "key1": [],
        },
        data={
            "key1": "hello",
        },
    )
    assert settings["key1"] == "hello"
    settings.load(fallback_option="default settings")
    assert settings["key1"] == "hello"
    settings["key1"] = "a"
    settings.load(fallback_option="default settings")
    assert settings["key1"] == "a"


def test_update() -> None:
    settings = Settings(
        settings_file_path="sample settings file name.json",
        default_factories={
            "key1": lambda: "value1",
        },
        default_settings={
            "key1": [],
        },
        data={
            "key1": "hello",
        },
    )
    assert settings["key1"] == "hello"
    settings.update({"key1": "a"})
    assert settings["key1"] == "a"
    settings.update({"key2": "b"})
    assert settings["key2"] == "b"


def test_Settings__is_using_json() -> None:
    settings = Settings(
        settings_file_path="sample_settings_file_name.json",
        default_factories={
            "key1": lambda: "value1",
        },
        data={
            "key1": "hello",
            "key2": "world",
        },
    )
    assert settings._Settings__is_using_json()
    settings.settings_file_path = "sample_settings_file_name.yaml"
    assert not settings._Settings__is_using_json()


def test_load_from_dict() -> None:
    settings = Settings()
    settings.load_from_dict(
        {
            "key1": "hello",
            "key2": "world",
        }
    )
    assert len(settings.data) == 0
    settings = Settings(
        data={
            "key1": "a",
            "key2": "b",
        }
    )
    settings.load_from_dict(
        {
            "key1": "c",
            "key2": "d",
        }
    )
    assert settings.data["key1"] == "c"
    assert settings.data["key2"] == "d"


def test_dump_to_dict() -> None:
    settings = Settings(
        settings_file_path="sample_settings_file_name.json",
        data={
            "key1": "hello",
            "key2": "world",
        },
    )
    assert settings.dump_to_dict() == {
        "key1": "hello",
        "key2": "world",
    }


def test_nested_Settings() -> None:
    settings = Settings(
        settings_file_path="sample_settings_file_name.json",
        default_settings={
            "key6": [],
            "key7": Settings(
                data={
                    "key8": "value8",
                }
            ),
        },
        data={
            "key1": "hello",
            "key2": "world",
            "key3": "value3",
            "key4": Settings(
                settings_file_path="why would anyone want an inner file though.yaml",
                data={
                    "key5": "value5",
                },
            ),
        },
    )
    assert settings.dump_to_dict() == {
        "key1": "hello",
        "key2": "world",
        "key3": "value3",
        "key4": {
            "key5": "value5",
        },
    }


def test_creating_setting_after_init() -> None:
    settings = Settings(
        settings_file_path="sample_settings_file_name.json",
        default_settings={
            "key1": [],
            "key2": "value2",
        },
    )
    with pytest.raises(KeyError):
        settings["key3"] = "value3"


def test_prompt_error() -> None:
    settings = Settings(
        settings_file_path="nonexistent file.json",
        default_settings={
            "key1": [],
            "key2": "value2",
        },
    )
    with pytest.raises(ValueError):
        settings.load(fallback_option="prompt user")


def test_nested_setting_loaders_and_dumpers() -> None:
    @dataclass
    class Coords:
        x: int
        y: int

        def __init__(self, x_and_y: Tuple[int, int]) -> None:
            self.x = x_and_y[0]
            self.y = x_and_y[1]

    settings = Settings(
        setting_loader=Coords,
        setting_dumper=lambda obj: (obj.x, obj.y),
        data={
            "location 1": Coords(x_and_y=(1, 2)),
            "location 2": Coords(x_and_y=(3, 4)),
            "patterns": Settings(
                setting_loader=re.compile,
                setting_dumper=lambda x: x.pattern,
                data={
                    "phone number pattern": re.compile(r"\d{3}-?\d{3}-?\d{4}"),
                    "email address pattern": re.compile(
                        r"[\w\d.+-]+@[\w\d.-]+\.[\w\d]+"
                    ),
                },
            ),
        },
    )
    settings_dict = settings.dump_to_dict()
    assert settings_dict["location 1"] == (1, 2)
    assert settings_dict["location 2"] == (3, 4)
    assert settings_dict["patterns"]["phone number pattern"] == r"\d{3}-?\d{3}-?\d{4}"
    assert (
        settings_dict["patterns"]["email address pattern"]
        == r"[\w\d.+-]+@[\w\d.-]+\.[\w\d]+"
    )
    settings.load_from_dict(settings_dict)
    assert settings["location 1"] == Coords(x_and_y=(1, 2))
    assert settings["location 2"] == Coords(x_and_y=(3, 4))
    assert settings["patterns"]["phone number pattern"] == re.compile(
        r"\d{3}-?\d{3}-?\d{4}"
    )
    assert settings["patterns"]["email address pattern"] == re.compile(
        r"[\w\d.+-]+@[\w\d.-]+\.[\w\d]+"
    )


def test_init_without_keywords() -> None:
    with pytest.raises(TypeError):
        Settings("sample settings file path.json")

[(9, 'app_settings_dict.Settings', 'Settings', ([], {'settings_file_path': '"""C:/Users/chris/Documents/sample_settings_file_name.json"""', 'default_factories': "{'key1': lambda : 'value1'}", 'data': "{'key1': 'hello', 'key2': 'world'}"}), False, 'from app_settings_dict import Settings\n'), (31, 'app_settings_dict.Settings', 'Settings', ([], {'settings_file_path': '"""sample settings file name.json"""', 'default_factories': "{'key1': lambda : 'value1', 'key2': lambda : 'value2', 'key3': lambda :\n    'value3'}", 'default_settings': "{'key3': []}", 'data': "{'key1': 'hello', 'key2': 'world'}"}), False, 'from app_settings_dict import Settings\n'), (66, 'app_settings_dict.Settings', 'Settings', ([], {'settings_file_path': '"""not a real file.yaml"""', 'prompt_user_for_all_settings': 'sample_prompt_function', 'default_factories': "{'key1': lambda : 'value1', 'key2': lambda : 'value2', 'key3': lambda :\n    'value3'}", 'default_settings': "{'key3': [], 'key4': 'value4'}", 'data': "{'key1': 'hello', 'key2': 'world'}"}), False, 'from app_settings_dict import Settings\n'), (108, 'app_settings_dict.Settings', 'Settings', ([], {'settings_file_path': '"""sample settings file name.json"""', 'prompt_user_for_all_settings': '(lambda : 1 / 0)', 'default_factories': "{'key1': lambda : 'value1'}", 'default_settings': "{'key1': []}", 'data': "{'key1': 'hello'}"}), False, 'from app_settings_dict import Settings\n'), (131, 'app_settings_dict.Settings', 'Settings', ([], {'settings_file_path': '"""sample settings file name.json"""', 'prompt_user_for_all_settings': "(lambda : {'key1': 'a', 'key2': 'b'})", 'default_factories': "{'key1': sample_prompt_function, 'key2': lambda : 'value2', 'key3': lambda :\n    'value3'}", 'default_settings': "{'key3': []}", 'data': "{'key1': 'hello', 'key2': 'world'}"}), False, 'from app_settings_dict import Settings\n'), (153, 'app_settings_dict.Settings', 'Settings', ([], {'settings_file_path': '"""sample settings file name.json"""', 'default_factories': "{'key1': lambda : 'value1'}", 'default_settings': "{'key1': []}", 'data': "{'key1': 'hello'}"}), False, 'from app_settings_dict import Settings\n'), (174, 'app_settings_dict.Settings', 'Settings', ([], {'settings_file_path': '"""sample settings file name.json"""', 'default_factories': "{'key1': lambda : 'value1'}", 'default_settings': "{'key1': []}", 'data': "{'key1': 'hello'}"}), False, 'from app_settings_dict import Settings\n'), (194, 'app_settings_dict.Settings', 'Settings', ([], {'settings_file_path': '"""sample_settings_file_name.json"""', 'default_factories': "{'key1': lambda : 'value1'}", 'data': "{'key1': 'hello', 'key2': 'world'}"}), False, 'from app_settings_dict import Settings\n'), (210, 'app_settings_dict.Settings', 'Settings', ([], {}), False, 'from app_settings_dict import Settings\n'), (218, 'app_settings_dict.Settings', 'Settings', ([], {'data': "{'key1': 'a', 'key2': 'b'}"}), False, 'from app_settings_dict import Settings\n'), (235, 'app_settings_dict.Settings', 'Settings', ([], {'settings_file_path': '"""sample_settings_file_name.json"""', 'data': "{'key1': 'hello', 'key2': 'world'}"}), False, 'from app_settings_dict import Settings\n'), (282, 'app_settings_dict.Settings', 'Settings', ([], {'settings_file_path': '"""sample_settings_file_name.json"""', 'default_settings': "{'key1': [], 'key2': 'value2'}"}), False, 'from app_settings_dict import Settings\n'), (294, 'app_settings_dict.Settings', 'Settings', ([], {'settings_file_path': '"""nonexistent file.json"""', 'default_settings': "{'key1': [], 'key2': 'value2'}"}), False, 'from app_settings_dict import Settings\n'), (26, 'pytest.raises', 'pytest.raises', (['KeyError'], {}), False, 'import pytest\n'), (90, 'pytest.raises', 'pytest.raises', (['KeyError'], {}), False, 'import pytest\n'), (103, 'pytest.raises', 'pytest.raises', (['ValueError'], {}), False, 'import pytest\n'), (289, 'pytest.raises', 'pytest.raises', (['KeyError'], {}), False, 'import pytest\n'), (301, 'pytest.raises', 'pytest.raises', (['ValueError'], {}), False, 'import pytest\n'), (344, 're.compile', 're.compile', (['"""\\\\d{3}-?\\\\d{3}-?\\\\d{4}"""'], {}), False, 'import re\n'), (347, 're.compile', 're.compile', (['"""[\\\\w\\\\d.+-]+@[\\\\w\\\\d.-]+\\\\.[\\\\w\\\\d]+"""'], {}), False, 'import re\n'), (353, 'pytest.raises', 'pytest.raises', (['TypeError'], {}), False, 'import pytest\n'), (354, 'app_settings_dict.Settings', 'Settings', (['"""sample settings file path.json"""'], {}), False, 'from app_settings_dict import Settings\n'), (253, 'app_settings_dict.Settings', 'Settings', ([], {'data': "{'key8': 'value8'}"}), False, 'from app_settings_dict import Settings\n'), (263, 'app_settings_dict.Settings', 'Settings', ([], {'settings_file_path': '"""why would anyone want an inner file though.yaml"""', 'data': "{'key5': 'value5'}"}), False, 'from app_settings_dict import Settings\n'), (325, 're.compile', 're.compile', (['"""\\\\d{3}-?\\\\d{3}-?\\\\d{4}"""'], {}), False, 'import re\n'), (326, 're.compile', 're.compile', (['"""[\\\\w\\\\d.+-]+@[\\\\w\\\\d.-]+\\\\.[\\\\w\\\\d]+"""'], {}), False, 'import re\n')]