"""
Gap tests for settings_utils: _get_typed_value edge cases,
get_bool_setting, get_settings_snapshot, extract_setting_value
corner cases, and extract_bool_setting with non-bool inputs.

These complement the existing test_settings_utils_typed_value.py and
test_settings_utils_behavior.py by covering paths that those files miss.
"""

import pytest

from local_deep_research.api.settings_utils import (
    InMemorySettingsManager,
    extract_bool_setting,
    extract_setting_value,
)


# ---------------------------------------------------------------------------
# Fixture
# ---------------------------------------------------------------------------


@pytest.fixture
def manager():
    """Create an InMemorySettingsManager with defaults loaded."""
    return InMemorySettingsManager()


# ---------------------------------------------------------------------------
# _get_typed_value — additional edge cases
# ---------------------------------------------------------------------------


class TestGetTypedValueTextarea:
    """textarea ui_element — should behave like text (str conversion)."""

    def test_string_passthrough(self, manager):
        result = manager._get_typed_value(
            {"ui_element": "textarea"}, "hello\nworld"
        )
        assert result == "hello\nworld"

    def test_int_converted_to_string(self, manager):
        result = manager._get_typed_value({"ui_element": "textarea"}, 99)
        assert result == "99"
        assert isinstance(result, str)


class TestGetTypedValueConversionEdgeCases:
    """Conversion failure and boundary cases not covered elsewhere."""

    def test_none_returns_none_regardless_of_ui_element(self, manager):
        """None short-circuits before any type dispatch."""
        for ui in ("text", "checkbox", "number", "select", "json", "textarea"):
            assert manager._get_typed_value({"ui_element": ui}, None) is None

    def test_number_with_empty_string(self, manager):
        """Empty string cannot be parsed as number; returns original value."""
        result = manager._get_typed_value({"ui_element": "number"}, "")
        assert result == ""

    def test_range_with_negative_float(self, manager):
        result = manager._get_typed_value({"ui_element": "range"}, "-2.5")
        assert result == -2.5

    def test_checkbox_with_none_value(self, manager):
        """None should be returned directly (short-circuit), not converted."""
        result = manager._get_typed_value({"ui_element": "checkbox"}, None)
        assert result is None

    def test_json_with_string_json_object(self, manager):
        """A JSON string should be parsed into a dict."""
        result = manager._get_typed_value({"ui_element": "json"}, '{"a": 1}')
        assert result == {"a": 1}

    def test_json_with_empty_string(self, manager):
        """Empty string for json ui_element — should pass through as-is."""
        result = manager._get_typed_value({"ui_element": "json"}, "")
        # _parse_json_value returns empty strings as-is
        assert result == ""

    def test_multiselect_with_empty_list(self, manager):
        result = manager._get_typed_value({"ui_element": "multiselect"}, [])
        assert result == []

    def test_multiselect_with_single_item_string(self, manager):
        """A plain string without commas should still be parsed."""
        result = manager._get_typed_value({"ui_element": "multiselect"}, "solo")
        # _parse_multiselect with a non-bracketed, non-comma string
        # returns a single-element list
        assert isinstance(result, list)
        assert "solo" in result


class TestGetTypedValueMissingUiElement:
    """When setting_data has no ui_element key at all."""

    def test_defaults_to_text_converts_to_str(self, manager):
        """Missing ui_element defaults to 'text', so str() is applied."""
        result = manager._get_typed_value({}, 42)
        assert result == "42"
        assert isinstance(result, str)


# ---------------------------------------------------------------------------
# get_bool_setting — InMemorySettingsManager method
# ---------------------------------------------------------------------------


class TestGetBoolSetting:
    """InMemorySettingsManager.get_bool_setting wraps get_setting + to_bool."""

    def test_true_bool_value(self, manager):
        manager.create_or_update_setting(
            {"key": "test.gbool", "value": True, "ui_element": "checkbox"}
        )
        assert manager.get_bool_setting("test.gbool") is True

    def test_false_bool_value(self, manager):
        manager.create_or_update_setting(
            {"key": "test.gbool", "value": False, "ui_element": "checkbox"}
        )
        assert manager.get_bool_setting("test.gbool") is False

    def test_string_true(self, manager):
        manager.create_or_update_setting(
            {"key": "test.gbool", "value": "true", "ui_element": "checkbox"}
        )
        assert manager.get_bool_setting("test.gbool") is True

    def test_string_on(self, manager):
        manager.create_or_update_setting(
            {"key": "test.gbool", "value": "on", "ui_element": "checkbox"}
        )
        assert manager.get_bool_setting("test.gbool") is True

    def test_missing_key_returns_default_true(self, manager):
        assert manager.get_bool_setting("nonexistent.key", default=True) is True

    def test_missing_key_returns_default_false(self, manager):
        assert (
            manager.get_bool_setting("nonexistent.key", default=False) is False
        )

    def test_integer_1_is_true(self, manager):
        manager.create_or_update_setting(
            {"key": "test.gbool", "value": 1, "ui_element": "checkbox"}
        )
        # parse_boolean treats any truthy value (including 1) as True
        assert manager.get_bool_setting("test.gbool") is True

    def test_integer_0_is_false(self, manager):
        manager.create_or_update_setting(
            {"key": "test.gbool", "value": 0, "ui_element": "checkbox"}
        )
        assert manager.get_bool_setting("test.gbool") is False


# ---------------------------------------------------------------------------
# get_settings_snapshot — simplified key→value dict
# ---------------------------------------------------------------------------


class TestGetSettingsSnapshot:
    """InMemorySettingsManager.get_settings_snapshot returns flat key→value."""

    def test_returns_dict(self, manager):
        snapshot = manager.get_settings_snapshot()
        assert isinstance(snapshot, dict)

    def test_values_are_extracted_from_setting_dicts(self, manager):
        """Each setting's 'value' field should be the snapshot value."""
        manager.create_or_update_setting(
            {"key": "snap.test", "value": "hello", "ui_element": "text"}
        )
        snapshot = manager.get_settings_snapshot()
        assert snapshot["snap.test"] == "hello"

    def test_non_dict_settings_passed_through(self, manager):
        """If a setting is not a dict (unlikely but possible), pass through."""
        # Force a raw value into _settings
        manager._settings["raw.val"] = "plain_string"
        snapshot = manager.get_settings_snapshot()
        assert snapshot["raw.val"] == "plain_string"

    def test_snapshot_is_independent_copy(self, manager):
        """Modifying snapshot should not affect manager state."""
        snapshot = manager.get_settings_snapshot()
        snapshot["injected"] = "bad"
        snapshot2 = manager.get_settings_snapshot()
        assert "injected" not in snapshot2


# ---------------------------------------------------------------------------
# create_or_update_setting — edge cases
# ---------------------------------------------------------------------------


class TestCreateOrUpdateSettingEdgeCases:
    def test_non_dict_returns_none(self, manager):
        """Passing a non-dict returns None."""
        assert manager.create_or_update_setting("not_a_dict") is None

    def test_dict_without_key_returns_none(self, manager):
        assert manager.create_or_update_setting({"value": 42}) is None

    def test_does_not_mutate_input_dict(self, manager):
        """The original dict should not be modified when value is typed."""
        original = {
            "key": "test.nomut",
            "value": "42",
            "ui_element": "number",
        }
        original_value = original["value"]
        manager.create_or_update_setting(original)
        # Original dict's value should remain unchanged
        assert original["value"] == original_value

    def test_setting_without_value_key(self, manager):
        """A dict with 'key' but no 'value' should still be stored."""
        result = manager.create_or_update_setting(
            {"key": "test.noval", "ui_element": "text"}
        )
        assert result is not None
        assert "test.noval" in manager.get_all_settings()


# ---------------------------------------------------------------------------
# import_settings — type conversion path
# ---------------------------------------------------------------------------


class TestImportSettingsTypedValues:
    """import_settings applies _get_typed_value when value dicts are imported."""

    def test_number_value_converted_on_import(self, manager):
        manager.import_settings(
            {"imp.num": {"value": "7", "ui_element": "number"}},
            delete_extra=True,
        )
        # The stored value should be int 7 (whole-number float → int)
        all_settings = manager.get_all_settings()
        assert all_settings["imp.num"]["value"] == 7

    def test_checkbox_value_converted_on_import(self, manager):
        manager.import_settings(
            {"imp.bool": {"value": "true", "ui_element": "checkbox"}},
            delete_extra=True,
        )
        all_settings = manager.get_all_settings()
        assert all_settings["imp.bool"]["value"] is True

    def test_plain_value_stored_as_is(self, manager):
        """Non-dict values are stored without type conversion."""
        manager.import_settings(
            {"imp.raw": "just_a_string"},
            delete_extra=True,
        )
        all_settings = manager.get_all_settings()
        assert all_settings["imp.raw"] == "just_a_string"


# ---------------------------------------------------------------------------
# extract_setting_value — additional corner cases
# ---------------------------------------------------------------------------


class TestExtractSettingValueEdgeCases:
    def test_dict_without_value_key_returns_whole_dict(self):
        """A dict that lacks 'value' is returned as-is (it's not a setting dict)."""
        settings = {"key": {"other_field": "data"}}
        result = extract_setting_value(settings, "key")
        assert result == {"other_field": "data"}

    def test_value_is_none_explicitly(self):
        """When value is explicitly None, it should be returned (not default)."""
        settings = {"key": {"value": None}}
        result = extract_setting_value(settings, "key", default="fallback")
        assert result is None

    def test_value_is_zero(self):
        """Falsy value 0 should be returned, not the default."""
        settings = {"key": {"value": 0}}
        result = extract_setting_value(settings, "key", default=99)
        assert result == 0

    def test_value_is_empty_string(self):
        """Falsy value '' should be returned, not the default."""
        settings = {"key": {"value": ""}}
        result = extract_setting_value(settings, "key", default="fallback")
        assert result == ""

    def test_value_is_false(self):
        """Falsy value False should be returned, not the default."""
        settings = {"key": {"value": False}}
        result = extract_setting_value(settings, "key", default=True)
        assert result is False

    def test_empty_snapshot(self):
        result = extract_setting_value({}, "any", default="d")
        assert result == "d"


# ---------------------------------------------------------------------------
# extract_bool_setting — additional edge cases
# ---------------------------------------------------------------------------


class TestExtractBoolSettingEdgeCases:
    def test_integer_1_is_true(self):
        settings = {"flag": {"value": 1}}
        assert extract_bool_setting(settings, "flag") is True

    def test_integer_0_is_false(self):
        settings = {"flag": {"value": 0}}
        assert extract_bool_setting(settings, "flag") is False

    def test_none_value_uses_default_true(self):
        settings = {"flag": {"value": None}}
        assert extract_bool_setting(settings, "flag", default=True) is True

    def test_none_value_uses_default_false(self):
        settings = {"flag": {"value": None}}
        assert extract_bool_setting(settings, "flag", default=False) is False

    def test_empty_string_is_false(self):
        """Empty string is not in the truthy set, so it's False."""
        settings = {"flag": {"value": ""}}
        assert extract_bool_setting(settings, "flag") is False

    def test_whitespace_string_true(self):
        """to_bool strips whitespace before comparison."""
        settings = {"flag": {"value": "  true  "}}
        assert extract_bool_setting(settings, "flag") is True

    def test_case_insensitive_TRUE(self):
        settings = {"flag": {"value": "TRUE"}}
        assert extract_bool_setting(settings, "flag") is True

    def test_plain_value_not_dict(self):
        """When value is stored directly (not in a dict with 'value' key)."""
        settings = {"flag": "yes"}
        assert extract_bool_setting(settings, "flag") is True

    def test_none_snapshot_returns_default(self):
        assert extract_bool_setting(None, "flag", default=True) is True

    def test_string_enabled_is_true(self):
        settings = {"flag": {"value": "enabled"}}
        assert extract_bool_setting(settings, "flag") is True

    def test_string_on_is_true(self):
        settings = {"flag": {"value": "on"}}
        assert extract_bool_setting(settings, "flag") is True

    def test_arbitrary_string_is_false(self):
        """An unrecognized string should be False."""
        settings = {"flag": {"value": "maybe"}}
        assert extract_bool_setting(settings, "flag") is False
