import shutil
from pathlib import Path
from typing import Type, Tuple, Optional

import pytest
from pydantic import BaseModel

from pyappconf.model import AppConfig, ConfigFormats, BaseConfig
from tests.config import (
    GENERATED_DATA_DIR,
    JSON_PATH,
    YAML_PATH,
    TOML_PATH,
    DATA_NAME,
    NON_EXISTENT_INPUT_JSON_PATH,
    NON_EXISTENT_NAME,
    RECURSIVE_INPUT_FOLDER,
    INPUT_DATA_DIR,
)
from tests.fixtures.model import (
    get_model_object,
    get_model_classes,
    model_classes,
    model_object,
    model_class_with_defaults,
    model_object_with_defaults,
)


def _save_load_test(custom_settings: AppConfig):
    mod = get_model_object(settings=custom_settings)
    mod.save()
    assert str(mod.settings.config_location).endswith(
        custom_settings.default_format.value
    )

    OrigConfig, SubConfig = get_model_classes()

    class MyConfig(OrigConfig):
        _settings = custom_settings

    assert str(MyConfig._settings.config_location).endswith(
        custom_settings.default_format.value
    )
    obj = MyConfig.load()
    assert mod == obj


def test_save_load_toml():
    custom_settings = AppConfig(
        app_name="MyApp", custom_config_folder=GENERATED_DATA_DIR
    )
    _save_load_test(custom_settings)


def test_save_load_yaml():
    custom_settings = AppConfig(
        app_name="MyApp",
        custom_config_folder=GENERATED_DATA_DIR,
        default_format=ConfigFormats.YAML,
    )
    _save_load_test(custom_settings)


def test_save_load_json():
    custom_settings = AppConfig(
        app_name="MyApp",
        custom_config_folder=GENERATED_DATA_DIR,
        default_format=ConfigFormats.JSON,
    )
    _save_load_test(custom_settings)


def assert_model_loaded_with_extension(
    mod: BaseConfig, model_object: BaseConfig, config_format: ConfigFormats
):
    assert mod.settings.custom_config_folder == INPUT_DATA_DIR
    assert mod.settings.default_format == config_format
    assert mod.settings.config_name == DATA_NAME

    mod.settings = model_object.settings
    assert mod == model_object


def test_load_toml_with_custom_path(
    model_object: BaseConfig, model_classes: Tuple[Type[BaseConfig], Type[BaseModel]]
):
    MyConfig, _ = model_classes
    mod = MyConfig.load(TOML_PATH)
    assert_model_loaded_with_extension(mod, model_object, ConfigFormats.TOML)


def test_load_yaml_with_custom_path(
    model_object: BaseConfig, model_classes: Tuple[Type[BaseConfig], Type[BaseModel]]
):
    MyConfig, _ = model_classes
    mod = MyConfig.load(YAML_PATH)
    assert_model_loaded_with_extension(mod, model_object, ConfigFormats.YAML)


def test_load_json_with_custom_path(
    model_object: BaseConfig, model_classes: Tuple[Type[BaseConfig], Type[BaseModel]]
):
    MyConfig, _ = model_classes
    mod = MyConfig.load(JSON_PATH)
    assert_model_loaded_with_extension(mod, model_object, ConfigFormats.JSON)


def test_load_or_create_with_path_exists(
    model_object: BaseConfig, model_classes: Tuple[Type[BaseConfig], Type[BaseModel]]
):
    MyConfig, _ = model_classes
    mod = MyConfig.load_or_create(JSON_PATH)
    assert_model_loaded_with_extension(mod, model_object, ConfigFormats.JSON)


def test_load_or_create_with_path_does_not_exist(
    model_object_with_defaults: BaseConfig, model_class_with_defaults: Type[BaseConfig]
):
    MyConfig = model_class_with_defaults
    model_object = model_object_with_defaults
    mod = MyConfig.load_or_create(NON_EXISTENT_INPUT_JSON_PATH)
    assert mod.settings.custom_config_folder == INPUT_DATA_DIR
    assert mod.settings.default_format == ConfigFormats.JSON
    assert mod.settings.config_name == NON_EXISTENT_NAME

    mod.settings = model_object.settings
    assert mod == model_object


@pytest.mark.parametrize(
    "path, expect_string",
    [
        (RECURSIVE_INPUT_FOLDER / "1", "loaded from 1"),
        (RECURSIVE_INPUT_FOLDER / "2", "loaded from recursive"),
        # Third and fourth cases - didn't find recursive, go to default location
        (INPUT_DATA_DIR, "a"),
        (None, "a"),
    ],
)
def test_load_recursive(path: Optional[Path], expect_string: str):
    OrigConfig, SubConfig = get_model_classes()

    shutil.copy(
        INPUT_DATA_DIR / "data.toml", GENERATED_DATA_DIR / "recursive-config.toml"
    )

    custom_settings = AppConfig(
        app_name="MyApp",
        custom_config_folder=GENERATED_DATA_DIR,
        config_name="recursive-config",
    )

    class MyConfig(OrigConfig):
        _settings = custom_settings

    mod = MyConfig.load_recursive(path)

    assert mod.string == expect_string
