"""Tests for django_devicehub.conf — DevicehubSettings."""

import pytest
from django.test import override_settings

from django_devicehub.conf import DEFAULTS, DevicehubSettings


# ---------------------------------------------------------------------------
# Default settings
# ---------------------------------------------------------------------------


class TestDefaults:
    def test_default_topic_prefix(self):
        """In test settings, TOPIC_PREFIX is overridden to 'test'."""
        from django_devicehub.conf import devicehub_settings

        assert devicehub_settings.TOPIC_PREFIX == "test"

    def test_default_storage_engine(self):
        from django_devicehub.conf import devicehub_settings

        assert (
            devicehub_settings.STORAGE["ENGINE"]
            == "django_devicehub.storage.django_orm.DjangoORMStorage"
        )

    def test_default_auto_api(self):
        from django_devicehub.conf import devicehub_settings

        assert devicehub_settings.AUTO_API is True

    def test_default_device_id_max_length(self):
        from django_devicehub.conf import devicehub_settings

        assert devicehub_settings.DEVICE_ID_MAX_LENGTH == 128

    def test_default_api_key_length(self):
        from django_devicehub.conf import devicehub_settings

        assert devicehub_settings.API_KEY_LENGTH == 64

    def test_default_heartbeat_timeout_multiplier(self):
        from django_devicehub.conf import devicehub_settings

        assert devicehub_settings.HEARTBEAT_TIMEOUT_MULTIPLIER == 2.5


# ---------------------------------------------------------------------------
# User settings override defaults
# ---------------------------------------------------------------------------


class TestUserOverrides:
    def test_topic_prefix_overridden_by_test_settings(self):
        """tests/settings.py sets TOPIC_PREFIX = 'test'."""
        s = DevicehubSettings()
        assert s.TOPIC_PREFIX == "test"

    def test_auto_admin_overridden_by_test_settings(self):
        """tests/settings.py sets AUTO_ADMIN = False."""
        s = DevicehubSettings()
        assert s.AUTO_ADMIN is False

    @override_settings(DJANGO_DEVICEHUB={"TOPIC_PREFIX": "custom", "AUTO_API": False})
    def test_override_via_settings(self):
        s = DevicehubSettings()
        assert s.TOPIC_PREFIX == "custom"
        assert s.AUTO_API is False

    @override_settings(DJANGO_DEVICEHUB={"TOPIC_PREFIX": "override"})
    def test_non_overridden_defaults_preserved(self):
        s = DevicehubSettings()
        assert s.TOPIC_PREFIX == "override"
        # These should still be defaults
        assert s.AUTO_API is True
        assert s.DEVICE_ID_MAX_LENGTH == 128


# ---------------------------------------------------------------------------
# Nested dict merging
# ---------------------------------------------------------------------------


class TestNestedDictMerging:
    @override_settings(
        DJANGO_DEVICEHUB={
            "BROKERS": {
                "default": {
                    "HOST": "mqtt.example.com",
                    "PORT": 8883,
                }
            }
        }
    )
    def test_nested_broker_override(self):
        s = DevicehubSettings()
        broker = s.BROKERS["default"]
        assert broker["HOST"] == "mqtt.example.com"
        assert broker["PORT"] == 8883
        # Keys not overridden should come from DEFAULTS
        assert broker["ENGINE"] == "django_devicehub.brokers.mqtt.MQTTBroker"
        assert broker["KEEPALIVE"] == 60
        assert broker["USE_TLS"] is False

    @override_settings(
        DJANGO_DEVICEHUB={
            "STORAGE": {
                "ENGINE": "myapp.storage.InfluxStorage",
                "HOST": "influx.local",
            }
        }
    )
    def test_storage_override_preserves_structure(self):
        s = DevicehubSettings()
        assert s.STORAGE["ENGINE"] == "myapp.storage.InfluxStorage"
        assert s.STORAGE["HOST"] == "influx.local"


# ---------------------------------------------------------------------------
# Unknown settings from user are preserved
# ---------------------------------------------------------------------------


class TestUnknownSettings:
    @override_settings(
        DJANGO_DEVICEHUB={
            "MY_CUSTOM_SETTING": "hello",
            "ANOTHER_THING": 42,
        }
    )
    def test_unknown_settings_accessible(self):
        s = DevicehubSettings()
        assert s.MY_CUSTOM_SETTING == "hello"
        assert s.ANOTHER_THING == 42

    @override_settings(
        DJANGO_DEVICEHUB={
            "BROKERS": {
                "default": {
                    "MY_EXTRA": True,
                }
            }
        }
    )
    def test_unknown_nested_keys_preserved(self):
        s = DevicehubSettings()
        assert s.BROKERS["default"]["MY_EXTRA"] is True
        # Defaults should still be present
        assert (
            s.BROKERS["default"]["ENGINE"] == "django_devicehub.brokers.mqtt.MQTTBroker"
        )


# ---------------------------------------------------------------------------
# invalidate_cache()
# ---------------------------------------------------------------------------


class TestInvalidateCache:
    def test_invalidate_clears_cache(self):
        s = DevicehubSettings()
        # Access to trigger caching
        _ = s.TOPIC_PREFIX
        assert s._cached is not None

        s.invalidate_cache()
        assert s._cached is None

    @override_settings(DJANGO_DEVICEHUB={"TOPIC_PREFIX": "before"})
    def test_invalidate_allows_fresh_read(self):
        s = DevicehubSettings()
        assert s.TOPIC_PREFIX == "before"

        # Simulate a settings change (normally done via override_settings)
        s.invalidate_cache()

        # After invalidation, it re-reads from Django settings
        # (still "before" because override_settings is still active)
        assert s.TOPIC_PREFIX == "before"


# ---------------------------------------------------------------------------
# Invalid attribute
# ---------------------------------------------------------------------------


class TestInvalidAttribute:
    def test_accessing_unknown_setting_raises(self):
        s = DevicehubSettings()
        with pytest.raises(AttributeError, match="Invalid django-devicehub setting"):
            _ = s.NONEXISTENT_SETTING


# ---------------------------------------------------------------------------
# DEFAULTS constant
# ---------------------------------------------------------------------------


class TestDefaultsConstant:
    def test_defaults_has_expected_keys(self):
        expected_keys = {
            "BROKERS",
            "STORAGE",
            "TOPIC_PREFIX",
            "AUTO_API",
            "AUTO_ADMIN",
            "DEVICE_ID_MAX_LENGTH",
            "API_KEY_LENGTH",
            "HEARTBEAT_TIMEOUT_MULTIPLIER",
        }
        assert set(DEFAULTS.keys()) == expected_keys

    def test_defaults_broker_has_default_key(self):
        assert "default" in DEFAULTS["BROKERS"]

    def test_defaults_topic_prefix(self):
        assert DEFAULTS["TOPIC_PREFIX"] == "things"
