"""Tests for django_devicehub.registry — DeviceTypeRegistry."""

import pytest

from django_devicehub.exceptions import RegistryError
from django_devicehub.registry import DeviceTypeRegistry

from tests.testapp.devices import SmartCamera, SoilSensor, WeatherStation
from tests.testapp.models import (
    SoilSensorDevice,
    SoilSensorMessage,
    SoilSensorReading,
    WeatherStationDevice,
    WeatherStationMessage,
    WeatherStationReading,
)


@pytest.fixture
def fresh_registry():
    """A clean DeviceTypeRegistry (not the global singleton)."""
    return DeviceTypeRegistry()


# ---------------------------------------------------------------------------
# register() and get()
# ---------------------------------------------------------------------------


class TestRegisterAndGet:
    def test_register_then_get(self, fresh_registry):
        fresh_registry.register(WeatherStation)
        assert fresh_registry.get("WeatherStation") is WeatherStation

    def test_get_is_case_insensitive(self, fresh_registry):
        fresh_registry.register(WeatherStation)
        assert fresh_registry.get("weatherstation") is WeatherStation
        assert fresh_registry.get("WEATHERSTATION") is WeatherStation
        assert fresh_registry.get("WeAtHeRsTaTiOn") is WeatherStation

    def test_get_unknown_returns_none(self, fresh_registry):
        assert fresh_registry.get("DoesNotExist") is None

    def test_duplicate_registration_same_class_is_idempotent(self, fresh_registry):
        fresh_registry.register(WeatherStation)
        fresh_registry.register(WeatherStation)  # should not raise
        assert fresh_registry.get("WeatherStation") is WeatherStation
        assert len(fresh_registry) == 1

    def test_duplicate_registration_different_class_raises(self, fresh_registry):
        fresh_registry.register(WeatherStation)

        # Create a different class with the same name
        FakeWeatherStation = type(
            "WeatherStation",
            (),
            {"__qualname__": "WeatherStation", "__module__": __name__},
        )

        with pytest.raises(RegistryError, match="name conflict"):
            fresh_registry.register(FakeWeatherStation)


# ---------------------------------------------------------------------------
# register_models() and get_models()
# ---------------------------------------------------------------------------


class TestRegisterModels:
    def test_register_models_then_get_models(self, fresh_registry):
        fresh_registry.register(WeatherStation)
        fresh_registry.register_models(
            WeatherStation,
            WeatherStationDevice,
            WeatherStationReading,
            WeatherStationMessage,
        )
        models = fresh_registry.get_models("WeatherStation")
        assert models == (
            WeatherStationDevice,
            WeatherStationReading,
            WeatherStationMessage,
        )

    def test_get_models_unknown_returns_none(self, fresh_registry):
        assert fresh_registry.get_models("Unknown") is None


# ---------------------------------------------------------------------------
# all() and all_with_models()
# ---------------------------------------------------------------------------


class TestAllAndAllWithModels:
    def test_all_returns_registered_types(self, fresh_registry):
        fresh_registry.register(WeatherStation)
        fresh_registry.register(SoilSensor)
        all_types = fresh_registry.all()
        assert WeatherStation in all_types
        assert SoilSensor in all_types
        assert len(all_types) == 2

    def test_all_with_models_returns_tuples(self, fresh_registry):
        fresh_registry.register(WeatherStation)
        fresh_registry.register_models(
            WeatherStation,
            WeatherStationDevice,
            WeatherStationReading,
            WeatherStationMessage,
        )
        result = fresh_registry.all_with_models()
        assert len(result) == 1
        dt_cls, dev, reading, msg = result[0]
        assert dt_cls is WeatherStation
        assert dev is WeatherStationDevice
        assert reading is WeatherStationReading
        assert msg is WeatherStationMessage

    def test_all_with_models_excludes_types_without_models(self, fresh_registry):
        fresh_registry.register(WeatherStation)
        fresh_registry.register(SoilSensor)
        # Only register models for WeatherStation
        fresh_registry.register_models(
            WeatherStation,
            WeatherStationDevice,
            WeatherStationReading,
            WeatherStationMessage,
        )
        result = fresh_registry.all_with_models()
        assert len(result) == 1
        assert result[0][0] is WeatherStation


# ---------------------------------------------------------------------------
# __len__, __contains__
# ---------------------------------------------------------------------------


class TestDunderMethods:
    def test_len_empty(self, fresh_registry):
        assert len(fresh_registry) == 0

    def test_len_populated(self, fresh_registry):
        fresh_registry.register(WeatherStation)
        fresh_registry.register(SoilSensor)
        fresh_registry.register(SmartCamera)
        assert len(fresh_registry) == 3

    def test_contains_registered(self, fresh_registry):
        fresh_registry.register(WeatherStation)
        assert "WeatherStation" in fresh_registry
        assert "weatherstation" in fresh_registry

    def test_contains_unregistered(self, fresh_registry):
        assert "WeatherStation" not in fresh_registry


# ---------------------------------------------------------------------------
# get_by_device_model()
# ---------------------------------------------------------------------------


class TestGetByDeviceModel:
    def test_returns_correct_device_type(self, fresh_registry):
        fresh_registry.register(WeatherStation)
        fresh_registry.register(SoilSensor)
        fresh_registry.register_models(
            WeatherStation,
            WeatherStationDevice,
            WeatherStationReading,
            WeatherStationMessage,
        )
        fresh_registry.register_models(
            SoilSensor,
            SoilSensorDevice,
            SoilSensorReading,
            SoilSensorMessage,
        )
        assert (
            fresh_registry.get_by_device_model(WeatherStationDevice) is WeatherStation
        )
        assert fresh_registry.get_by_device_model(SoilSensorDevice) is SoilSensor

    def test_returns_none_for_unknown_model(self, fresh_registry):
        assert fresh_registry.get_by_device_model(WeatherStationDevice) is None


# ---------------------------------------------------------------------------
# clear()
# ---------------------------------------------------------------------------


class TestClear:
    def test_clear_empties_registry(self, fresh_registry):
        fresh_registry.register(WeatherStation)
        fresh_registry.register_models(
            WeatherStation,
            WeatherStationDevice,
            WeatherStationReading,
            WeatherStationMessage,
        )
        assert len(fresh_registry) == 1

        fresh_registry.clear()

        assert len(fresh_registry) == 0
        assert fresh_registry.get("WeatherStation") is None
        assert fresh_registry.get_models("WeatherStation") is None
        assert fresh_registry.all() == []
        assert fresh_registry.all_with_models() == []


# ---------------------------------------------------------------------------
# Global registry (singleton) — verify testapp types are auto-registered
# ---------------------------------------------------------------------------


class TestGlobalRegistry:
    """The global registry instance at django_devicehub.registry.registry should contain
    the device types auto-registered by the metaclass when tests.testapp.devices
    is imported."""

    def test_global_registry_contains_testapp_types(self):
        from django_devicehub.registry import registry

        assert "WeatherStation" in registry
        assert "SoilSensor" in registry
        assert "SmartCamera" in registry
