import sys
import os
import importlib
from django.test import SimpleTestCase
from unittest.mock import patch, MagicMock

import redis as redis_module

# Ensure the scripts directory is importable
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'scripts'))


def _import_wait_for_redis():
    """Import (or reimport) the wait_for_redis function from scripts/."""
    import wait_for_redis as module
    importlib.reload(module)
    return module.wait_for_redis


class WaitForRedisTests(SimpleTestCase):
    """
    Tests for scripts/wait_for_redis.py.

    Verifies flush behaviour: full flushdb in AIO mode, selective
    (non-Celery) key deletion in modular mode.
    """

    @patch('wait_for_redis.redis.Redis')
    def test_aio_mode_calls_flushdb(self, mock_redis_cls):
        """In AIO mode (default), flushdb is called after successful ping."""
        mock_client = MagicMock()
        mock_client.ping.return_value = True
        mock_redis_cls.return_value = mock_client

        with patch.dict(os.environ, {}, clear=False):
            os.environ.pop('DISPATCHARR_ENV', None)
            wait_for_redis = _import_wait_for_redis()
            result = wait_for_redis(max_retries=1, retry_interval=0)

        self.assertTrue(result)
        mock_client.flushdb.assert_called_once()

    @patch('wait_for_redis.redis.Redis')
    def test_modular_mode_does_not_call_flushdb(self, mock_redis_cls):
        """In modular mode, flushdb must NOT be called — selective flush instead."""
        mock_client = MagicMock()
        mock_client.ping.return_value = True
        mock_redis_cls.return_value = mock_client

        with patch.dict(os.environ, {'DISPATCHARR_ENV': 'modular'}):
            wait_for_redis = _import_wait_for_redis()
            # Patch after reload so the mock isn't overwritten by module re-execution
            with patch('wait_for_redis._flush_non_celery_keys') as mock_selective:
                result = wait_for_redis(max_retries=1, retry_interval=0)

        self.assertTrue(result)
        mock_client.flushdb.assert_not_called()
        mock_selective.assert_called_once_with(mock_client)

    @patch('wait_for_redis.redis.Redis')
    def test_retries_on_connection_error(self, mock_redis_cls):
        """Should retry on ConnectionError and eventually succeed."""
        mock_client = MagicMock()
        mock_client.ping.side_effect = [
            redis_module.exceptions.ConnectionError("refused"),
            redis_module.exceptions.ConnectionError("refused"),
            True,
        ]
        mock_redis_cls.return_value = mock_client

        wait_for_redis = _import_wait_for_redis()
        result = wait_for_redis(max_retries=5, retry_interval=0)

        self.assertTrue(result)
        self.assertEqual(mock_client.ping.call_count, 3)

    @patch('wait_for_redis.redis.Redis')
    def test_returns_false_after_max_retries(self, mock_redis_cls):
        """Should return False when max retries are exhausted."""
        mock_client = MagicMock()
        mock_client.ping.side_effect = redis_module.exceptions.ConnectionError("refused")
        mock_redis_cls.return_value = mock_client

        wait_for_redis = _import_wait_for_redis()
        result = wait_for_redis(max_retries=2, retry_interval=0)

        self.assertFalse(result)

    @patch('wait_for_redis.redis.Redis')
    def test_unexpected_error_returns_false(self, mock_redis_cls):
        """Generic exceptions should return False immediately."""
        mock_client = MagicMock()
        mock_client.ping.side_effect = RuntimeError("unexpected")
        mock_redis_cls.return_value = mock_client

        wait_for_redis = _import_wait_for_redis()
        result = wait_for_redis(max_retries=5, retry_interval=0)

        self.assertFalse(result)
