"""Tests for sluice._config — auto-configuration of Celery flags."""

from __future__ import annotations

import logging
import types
from typing import TYPE_CHECKING

from sluice._config import (
    _CRITICAL_FLAGS,
    _NEVER_TOUCH,
    _is_explicitly_set,
    auto_configure,
    is_django_project,
)

if TYPE_CHECKING:
    import pytest
    from celery import Celery


class TestAutoConfigureDefaultApp:
    """Test auto_configure() on a fresh Celery app with default settings."""

    def test_sets_all_three_flags(self, celery_app: Celery) -> None:
        """All three critical flags should be set to True on a fresh app."""
        auto_configure(celery_app)

        assert celery_app.conf.worker_send_task_events is True
        assert celery_app.conf.task_send_sent_event is True
        assert celery_app.conf.task_track_started is True

    def test_returns_messages_for_each_enabled_flag(self, celery_app: Celery) -> None:
        """auto_configure() returns a message per flag it enables."""
        messages = auto_configure(celery_app)
        assert len(messages) == 3
        for msg in messages:
            assert "[sluice] Enabled" in msg

    def test_logs_info_for_each_enabled_flag(
        self, celery_app: Celery, caplog: pytest.LogCaptureFixture
    ) -> None:
        """Each enabled flag should produce an INFO log."""
        with caplog.at_level(logging.INFO, logger="sluice"):
            auto_configure(celery_app)

        assert "worker_send_task_events=True" in caplog.text
        assert "task_send_sent_event=True" in caplog.text
        assert "task_track_started=True" in caplog.text

    def test_idempotent_second_call(self, celery_app: Celery) -> None:
        """Calling auto_configure() twice should be a no-op the second time."""
        auto_configure(celery_app)
        messages = auto_configure(celery_app)
        # All flags are already True, so no messages on second call.
        assert messages == []


class TestAutoConfigureExplicitlyDisabled:
    """Test behavior when user explicitly sets flags to False."""

    def test_warns_on_explicitly_disabled_flag(
        self, celery_app: Celery, caplog: pytest.LogCaptureFixture
    ) -> None:
        """Warn when a user explicitly sets a flag to False."""
        # Simulate user explicitly setting the flag.
        celery_app.conf.worker_send_task_events = False
        # Mark it as explicitly set by adding to changes.
        if not hasattr(celery_app.conf, "changes"):
            celery_app.conf.changes = {}
        celery_app.conf.changes["worker_send_task_events"] = False

        with caplog.at_level(logging.WARNING, logger="sluice"):
            messages = auto_configure(celery_app)

        # Should warn about the explicitly disabled flag.
        warned = [m for m in messages if "Warning" in m]
        assert len(warned) >= 1
        assert "worker_send_task_events" in warned[0]
        assert "explicitly set to False" in warned[0]

    def test_does_not_override_explicitly_disabled_flag(
        self, celery_app: Celery
    ) -> None:
        """An explicitly disabled flag must NOT be overridden."""
        celery_app.conf.worker_send_task_events = False
        if not hasattr(celery_app.conf, "changes"):
            celery_app.conf.changes = {}
        celery_app.conf.changes["worker_send_task_events"] = False

        auto_configure(celery_app)

        # The flag should still be False — we don't override user intent.
        assert celery_app.conf.worker_send_task_events is False

    def test_other_flags_still_enabled(self, celery_app: Celery) -> None:
        """Flags not explicitly disabled should still be auto-enabled."""
        celery_app.conf.worker_send_task_events = False
        if not hasattr(celery_app.conf, "changes"):
            celery_app.conf.changes = {}
        celery_app.conf.changes["worker_send_task_events"] = False

        auto_configure(celery_app)

        # These were not explicitly disabled, so they should be enabled.
        assert celery_app.conf.task_send_sent_event is True
        assert celery_app.conf.task_track_started is True

    def test_warning_includes_docs_url(
        self, celery_app: Celery, caplog: pytest.LogCaptureFixture
    ) -> None:
        """Warning messages include a troubleshooting URL."""
        celery_app.conf.task_track_started = False
        if not hasattr(celery_app.conf, "changes"):
            celery_app.conf.changes = {}
        celery_app.conf.changes["task_track_started"] = False

        with caplog.at_level(logging.WARNING, logger="sluice"):
            auto_configure(celery_app)

        assert "https://docs.sluice.sh/config/celery-flags" in caplog.text


class TestAutoConfigurePresetFlags:
    """Test when flags are already correctly set."""

    def test_no_changes_when_all_flags_true(self, celery_app: Celery) -> None:
        """No messages when all flags are already True."""
        celery_app.conf.worker_send_task_events = True
        celery_app.conf.task_send_sent_event = True
        celery_app.conf.task_track_started = True

        messages = auto_configure(celery_app)
        assert messages == []


class TestNeverTouchFlags:
    """Verify that we never touch worker_hijack_root_logger."""

    def test_hijack_root_logger_not_in_critical_flags(self) -> None:
        """worker_hijack_root_logger must not be in the critical flags dict."""
        assert "worker_hijack_root_logger" not in _CRITICAL_FLAGS

    def test_hijack_root_logger_in_never_touch(self) -> None:
        """worker_hijack_root_logger must be in the never-touch set."""
        assert "worker_hijack_root_logger" in _NEVER_TOUCH

    def test_hijack_root_logger_not_modified(self, celery_app: Celery) -> None:
        """auto_configure() must not modify worker_hijack_root_logger."""
        original = celery_app.conf.worker_hijack_root_logger
        auto_configure(celery_app)
        assert celery_app.conf.worker_hijack_root_logger == original


class TestDjangoDetection:
    """Test Django auto-detection logic."""

    def test_detects_django_via_env_var(self, monkeypatch: pytest.MonkeyPatch) -> None:
        """is_django_project() returns True when DJANGO_SETTINGS_MODULE is set."""
        monkeypatch.setenv("DJANGO_SETTINGS_MODULE", "myproject.settings")
        assert is_django_project() is True

    def test_no_django_when_env_unset(self, monkeypatch: pytest.MonkeyPatch) -> None:
        """is_django_project() returns False when no Django indicators exist."""
        monkeypatch.delenv("DJANGO_SETTINGS_MODULE", raising=False)
        # Even if django is importable, settings.configured should be False
        # in a non-Django test environment.
        result = is_django_project()
        # We can't guarantee django isn't installed, but env var is the
        # primary check. Without it, this should be False in CI.
        assert isinstance(result, bool)

    def test_no_django_when_env_empty(self, monkeypatch: pytest.MonkeyPatch) -> None:
        """is_django_project() returns False when DJANGO_SETTINGS_MODULE is empty."""
        monkeypatch.setenv("DJANGO_SETTINGS_MODULE", "")
        # Empty string is falsy, so should not detect Django.
        assert is_django_project() is False


class TestIsExplicitlySet:
    """Tests for the _is_explicitly_set() helper."""

    def test_detects_flag_via_order_attribute(self, celery_app: Celery) -> None:
        """_is_explicitly_set() should detect flags via _order."""
        # Simulate _order attribute containing the flag.
        celery_app.conf._order = ["worker_send_task_events"]
        assert _is_explicitly_set(celery_app, "worker_send_task_events") is True
        assert _is_explicitly_set(celery_app, "task_track_started") is False

    def test_last_resort_fallback_no_order_no_changes(self) -> None:
        """_is_explicitly_set() falls back to value check when no _order or changes."""
        from unittest.mock import MagicMock

        # Create a mock app where conf has no _order and no changes.
        app = MagicMock()
        conf = types.SimpleNamespace(worker_send_task_events=False)
        app.conf = conf

        # No _order, no changes — falls through to last resort.
        # Value is False (default) → not explicitly set.
        assert _is_explicitly_set(app, "worker_send_task_events") is False

        # Value is True → differs from default → treated as explicitly set.
        conf.worker_send_task_events = True
        assert _is_explicitly_set(app, "worker_send_task_events") is True


class TestDjangoDetectionEdgeCases:
    """Edge cases for Django auto-detection."""

    def test_django_detection_handles_unexpected_exception(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        """is_django_project() returns False on unexpected exceptions."""
        monkeypatch.delenv("DJANGO_SETTINGS_MODULE", raising=False)

        # Make django import succeed but settings access raise something unexpected.
        import types

        fake_django = types.ModuleType("django")
        fake_conf = types.ModuleType("django.conf")

        class BrokenSettings:
            @property
            def configured(self) -> bool:
                raise OSError("simulated unexpected error")

        fake_conf.settings = BrokenSettings()  # type: ignore[attr-defined]
        fake_django.conf = fake_conf  # type: ignore[attr-defined]

        monkeypatch.setitem(__import__("sys").modules, "django", fake_django)
        monkeypatch.setitem(__import__("sys").modules, "django.conf", fake_conf)

        assert is_django_project() is False


class TestCriticalFlagsContract:
    """Verify the critical flags contract stays correct."""

    def test_exactly_three_critical_flags(self) -> None:
        """There should be exactly 3 critical flags."""
        assert len(_CRITICAL_FLAGS) == 3

    def test_critical_flag_names(self) -> None:
        """The critical flags are the three identified in the Celery deep-dive."""
        expected = {
            "worker_send_task_events",
            "task_send_sent_event",
            "task_track_started",
        }
        assert set(_CRITICAL_FLAGS.keys()) == expected

    def test_all_critical_flags_want_true(self) -> None:
        """All critical flags should be set to True."""
        for flag, value in _CRITICAL_FLAGS.items():
            assert value is True, f"{flag} should be True"
