"""
Tests for rate limiter tracker quality stats and maintenance methods.

Covers previously untested public methods:
- get_search_quality_stats() - search quality analysis from recent attempts
- get_stats() - programmatic mode code path (has a bug: NameError on line 558)
- cleanup_old_data() - old data removal in programmatic mode
- reset_engine() - memory-only reset in programmatic mode
"""

import time
from collections import deque
from unittest.mock import patch

from local_deep_research.config.thread_settings import NoSettingsContextError


def _make_tracker(**overrides):
    """Create a tracker in programmatic mode (no DB, no Flask)."""
    with patch(
        "local_deep_research.web_search_engines.rate_limiting.tracker.get_setting_from_snapshot"
    ) as mock_gs:
        mock_gs.side_effect = NoSettingsContextError("test")

        with patch(
            "local_deep_research.web_search_engines.rate_limiting.tracker.logger"
        ):
            from local_deep_research.web_search_engines.rate_limiting.tracker import (
                AdaptiveRateLimitTracker,
            )

            defaults = {"programmatic_mode": True}
            defaults.update(overrides)
            tracker = AdaptiveRateLimitTracker(**defaults)

    if "enabled" not in overrides:
        tracker.enabled = True

    return tracker


def _seed_attempts(tracker, engine, attempts_data):
    """Seed a tracker's recent_attempts with pre-built attempt dicts."""
    if engine not in tracker.recent_attempts:
        tracker.recent_attempts[engine] = deque(maxlen=tracker.memory_window)
    for item in attempts_data:
        tracker.recent_attempts[engine].append(item)


# ── get_search_quality_stats ────────────────────────────────────────


class TestGetSearchQualityStats:
    """Tests for get_search_quality_stats (lines 651-697)."""

    def test_empty_tracker_returns_empty_list(self):
        """No recent attempts → empty stats list."""
        tracker = _make_tracker()

        result = tracker.get_search_quality_stats()

        assert result == []

    def test_single_engine_basic_stats(self):
        """Single engine with result counts produces correct stats."""
        tracker = _make_tracker()
        _seed_attempts(
            tracker,
            "google",
            [
                {"search_result_count": 10, "success": True},
                {"search_result_count": 20, "success": True},
                {"search_result_count": 30, "success": True},
            ],
        )

        result = tracker.get_search_quality_stats()

        assert len(result) == 1
        stat = result[0]
        assert stat["engine_type"] == "google"
        assert stat["recent_avg_results"] == 20.0
        assert stat["min_recent_results"] == 10
        assert stat["max_recent_results"] == 30
        assert stat["sample_size"] == 3
        assert stat["total_attempts"] == 3
        assert stat["status"] == "EXCELLENT"

    def test_multiple_engines(self):
        """Stats returned for each engine with data."""
        tracker = _make_tracker()
        _seed_attempts(
            tracker,
            "google",
            [{"search_result_count": 15, "success": True}],
        )
        _seed_attempts(
            tracker,
            "bing",
            [{"search_result_count": 2, "success": True}],
        )

        result = tracker.get_search_quality_stats()

        engines = {s["engine_type"] for s in result}
        assert engines == {"google", "bing"}

    def test_filter_by_engine_type(self):
        """Passing engine_type filters to that engine only."""
        tracker = _make_tracker()
        _seed_attempts(
            tracker,
            "google",
            [{"search_result_count": 10, "success": True}],
        )
        _seed_attempts(
            tracker,
            "bing",
            [{"search_result_count": 5, "success": True}],
        )

        result = tracker.get_search_quality_stats(engine_type="google")

        assert len(result) == 1
        assert result[0]["engine_type"] == "google"

    def test_filter_nonexistent_engine_returns_empty(self):
        """Filtering by engine with no data returns empty list."""
        tracker = _make_tracker()
        _seed_attempts(
            tracker,
            "google",
            [{"search_result_count": 10, "success": True}],
        )

        result = tracker.get_search_quality_stats(engine_type="nonexistent")

        assert result == []

    def test_attempts_without_search_result_count_excluded(self):
        """Attempts missing search_result_count are excluded from averages."""
        tracker = _make_tracker()
        _seed_attempts(
            tracker,
            "google",
            [
                {"search_result_count": 10, "success": True},
                {"success": True},  # no search_result_count
                {"search_result_count": None, "success": True},
                {"search_result_count": 20, "success": True},
            ],
        )

        result = tracker.get_search_quality_stats()

        assert len(result) == 1
        stat = result[0]
        assert stat["sample_size"] == 2  # only 10 and 20
        assert stat["total_attempts"] == 4  # all 4 attempts counted
        assert stat["recent_avg_results"] == 15.0

    def test_all_attempts_missing_count_skips_engine(self):
        """Engine with no valid search_result_count is skipped entirely."""
        tracker = _make_tracker()
        _seed_attempts(
            tracker,
            "google",
            [
                {"success": True},
                {"search_result_count": None, "success": True},
            ],
        )

        result = tracker.get_search_quality_stats()

        assert result == []

    def test_zero_result_count_included(self):
        """search_result_count of 0 is a valid count (not excluded)."""
        tracker = _make_tracker()
        _seed_attempts(
            tracker,
            "google",
            [
                {"search_result_count": 0, "success": True},
                {"search_result_count": 0, "success": True},
            ],
        )

        result = tracker.get_search_quality_stats()

        assert len(result) == 1
        assert result[0]["recent_avg_results"] == 0.0
        assert result[0]["status"] == "CRITICAL"

    def test_quality_status_critical(self):
        """Average < 1 → CRITICAL status."""
        tracker = _make_tracker()
        _seed_attempts(
            tracker,
            "engine",
            [{"search_result_count": 0, "success": True}],
        )

        result = tracker.get_search_quality_stats()
        assert result[0]["status"] == "CRITICAL"

    def test_quality_status_warning(self):
        """Average >= 1 and < 3 → WARNING status."""
        tracker = _make_tracker()
        _seed_attempts(
            tracker,
            "engine",
            [{"search_result_count": 2, "success": True}],
        )

        result = tracker.get_search_quality_stats()
        assert result[0]["status"] == "WARNING"

    def test_quality_status_caution(self):
        """Average >= 3 and < 5 → CAUTION status."""
        tracker = _make_tracker()
        _seed_attempts(
            tracker,
            "engine",
            [{"search_result_count": 4, "success": True}],
        )

        result = tracker.get_search_quality_stats()
        assert result[0]["status"] == "CAUTION"

    def test_quality_status_good(self):
        """Average >= 5 and < 10 → GOOD status."""
        tracker = _make_tracker()
        _seed_attempts(
            tracker,
            "engine",
            [{"search_result_count": 7, "success": True}],
        )

        result = tracker.get_search_quality_stats()
        assert result[0]["status"] == "GOOD"

    def test_quality_status_excellent(self):
        """Average >= 10 → EXCELLENT status."""
        tracker = _make_tracker()
        _seed_attempts(
            tracker,
            "engine",
            [{"search_result_count": 15, "success": True}],
        )

        result = tracker.get_search_quality_stats()
        assert result[0]["status"] == "EXCELLENT"

    def test_min_max_with_single_attempt(self):
        """Min and max are the same with a single attempt."""
        tracker = _make_tracker()
        _seed_attempts(
            tracker,
            "google",
            [{"search_result_count": 5, "success": True}],
        )

        result = tracker.get_search_quality_stats()
        stat = result[0]
        assert stat["min_recent_results"] == 5
        assert stat["max_recent_results"] == 5

    def test_large_variance_in_results(self):
        """Correctly computes stats with large variance in result counts."""
        tracker = _make_tracker()
        _seed_attempts(
            tracker,
            "google",
            [
                {"search_result_count": 1, "success": True},
                {"search_result_count": 100, "success": True},
            ],
        )

        result = tracker.get_search_quality_stats()
        stat = result[0]
        assert stat["recent_avg_results"] == 50.5
        assert stat["min_recent_results"] == 1
        assert stat["max_recent_results"] == 100
        assert stat["status"] == "EXCELLENT"

    def test_quality_status_boundary_at_1(self):
        """Boundary: exactly 1.0 → WARNING (not CRITICAL)."""
        tracker = _make_tracker()
        _seed_attempts(
            tracker,
            "engine",
            [{"search_result_count": 1, "success": True}],
        )

        result = tracker.get_search_quality_stats()
        assert result[0]["status"] == "WARNING"

    def test_quality_status_boundary_at_3(self):
        """Boundary: exactly 3.0 → CAUTION (not WARNING)."""
        tracker = _make_tracker()
        _seed_attempts(
            tracker,
            "engine",
            [{"search_result_count": 3, "success": True}],
        )

        result = tracker.get_search_quality_stats()
        assert result[0]["status"] == "CAUTION"

    def test_quality_status_boundary_at_5(self):
        """Boundary: exactly 5.0 → GOOD (not CAUTION)."""
        tracker = _make_tracker()
        _seed_attempts(
            tracker,
            "engine",
            [{"search_result_count": 5, "success": True}],
        )

        result = tracker.get_search_quality_stats()
        assert result[0]["status"] == "GOOD"

    def test_quality_status_boundary_at_10(self):
        """Boundary: exactly 10.0 → EXCELLENT (not GOOD)."""
        tracker = _make_tracker()
        _seed_attempts(
            tracker,
            "engine",
            [{"search_result_count": 10, "success": True}],
        )

        result = tracker.get_search_quality_stats()
        assert result[0]["status"] == "EXCELLENT"


# ── get_stats programmatic mode ─────────────────────────────────────


class TestGetStatsProgrammaticMode:
    """Tests for get_stats() in programmatic mode (line 556-558).

    In the current code, programmatic_mode hits `return stats` at line 558
    but `stats` is only defined in the CI branch above (line 534).
    This is a real bug: NameError when programmatic_mode=True and
    is_ci_environment() is False.
    """

    @patch(
        "local_deep_research.web_search_engines.rate_limiting.tracker.is_ci_environment",
        return_value=True,
    )
    def test_get_stats_ci_mode_empty(self, _mock_ci):
        """In CI mode with no estimates, returns empty list."""
        tracker = _make_tracker()

        result = tracker.get_stats()

        assert result == []

    @patch(
        "local_deep_research.web_search_engines.rate_limiting.tracker.is_ci_environment",
        return_value=True,
    )
    def test_get_stats_ci_mode_with_estimates(self, _mock_ci):
        """In CI mode with estimates, returns in-memory stats."""
        tracker = _make_tracker()
        tracker.current_estimates["google"] = {
            "base": 2.0,
            "min": 1.0,
            "max": 5.0,
            "confidence": 0.8,
        }
        tracker.recent_attempts["google"] = deque(
            [{"success": True}], maxlen=100
        )

        result = tracker.get_stats()

        assert len(result) == 1
        engine, base, min_w, max_w, timestamp, attempts, confidence = result[0]
        assert engine == "google"
        assert base == 2.0
        assert min_w == 1.0
        assert max_w == 5.0
        assert attempts == 1
        assert confidence == 0.8

    @patch(
        "local_deep_research.web_search_engines.rate_limiting.tracker.is_ci_environment",
        return_value=True,
    )
    def test_get_stats_ci_mode_filter_by_engine(self, _mock_ci):
        """In CI mode, engine_type filter returns only matching engine."""
        tracker = _make_tracker()
        tracker.current_estimates["google"] = {
            "base": 2.0,
            "min": 1.0,
            "max": 5.0,
        }
        tracker.current_estimates["bing"] = {
            "base": 3.0,
            "min": 1.5,
            "max": 6.0,
        }

        result = tracker.get_stats(engine_type="google")

        assert len(result) == 1
        assert result[0][0] == "google"

    @patch(
        "local_deep_research.web_search_engines.rate_limiting.tracker.is_ci_environment",
        return_value=True,
    )
    def test_get_stats_ci_mode_nonexistent_engine(self, _mock_ci):
        """In CI mode, filtering by nonexistent engine returns empty."""
        tracker = _make_tracker()
        tracker.current_estimates["google"] = {
            "base": 2.0,
            "min": 1.0,
            "max": 5.0,
        }

        result = tracker.get_stats(engine_type="nonexistent")

        assert result == []

    @patch(
        "local_deep_research.web_search_engines.rate_limiting.tracker.is_ci_environment",
        return_value=True,
    )
    def test_get_stats_ci_mode_missing_confidence(self, _mock_ci):
        """In CI mode, missing confidence key defaults to 0.0."""
        tracker = _make_tracker()
        tracker.current_estimates["google"] = {
            "base": 2.0,
            "min": 1.0,
            "max": 5.0,
            # no "confidence" key
        }
        tracker.recent_attempts["google"] = deque(maxlen=100)

        result = tracker.get_stats()

        assert len(result) == 1
        confidence = result[0][6]
        assert confidence == 0.0

    @patch(
        "local_deep_research.web_search_engines.rate_limiting.tracker.is_ci_environment",
        return_value=True,
    )
    def test_get_stats_ci_mode_no_recent_attempts(self, _mock_ci):
        """In CI mode, engine with estimate but no recent_attempts key."""
        tracker = _make_tracker()
        tracker.current_estimates["google"] = {
            "base": 2.0,
            "min": 1.0,
            "max": 5.0,
        }
        # Don't add to recent_attempts

        result = tracker.get_stats()

        assert len(result) == 1
        attempts_count = result[0][5]
        assert attempts_count == 0

    @patch(
        "local_deep_research.web_search_engines.rate_limiting.tracker.is_ci_environment",
        return_value=False,
    )
    def test_get_stats_programmatic_mode_returns_in_memory_stats(
        self, _mock_ci
    ):
        """programmatic_mode=True + non-CI returns in-memory stats."""
        tracker = _make_tracker()

        # Pre-populate in-memory data
        with tracker._cache_lock:
            tracker.current_estimates["test_engine"] = {
                "base": 1.0,
                "min": 0.5,
                "max": 2.0,
                "confidence": 0.9,
            }
            tracker.recent_attempts["test_engine"] = [1, 2, 3]

        result = tracker.get_stats()
        assert len(result) == 1
        engine, base, min_w, max_w, _, attempts, confidence = result[0]
        assert engine == "test_engine"
        assert base == 1.0
        assert min_w == 0.5
        assert max_w == 2.0
        assert attempts == 3
        assert confidence == 0.9


# ── cleanup_old_data ────────────────────────────────────────────────


class TestCleanupOldData:
    """Tests for cleanup_old_data (lines 712-751)."""

    def test_programmatic_mode_returns_early(self):
        """In programmatic mode, cleanup does nothing (no DB access)."""
        tracker = _make_tracker()

        # Should not raise, just return early
        tracker.cleanup_old_data()

    def test_programmatic_mode_accepts_days_param(self):
        """Days parameter is accepted even in programmatic mode."""
        tracker = _make_tracker()

        tracker.cleanup_old_data(days=1)
        tracker.cleanup_old_data(days=365)

    @patch(
        "local_deep_research.web_search_engines.rate_limiting.tracker.is_ci_environment",
        return_value=True,
    )
    def test_ci_mode_returns_early(self, _mock_ci):
        """In CI/fallback mode, cleanup does nothing."""
        tracker = _make_tracker(programmatic_mode=False)

        tracker.cleanup_old_data()


# ── reset_engine programmatic mode ──────────────────────────────────


class TestResetEngineProgrammatic:
    """Tests for reset_engine in programmatic mode (lines 596-649)."""

    def test_clears_recent_attempts(self):
        """Reset removes engine from recent_attempts."""
        tracker = _make_tracker()
        tracker.recent_attempts["google"] = deque(
            [{"success": True}], maxlen=100
        )

        tracker.reset_engine("google")

        assert "google" not in tracker.recent_attempts

    def test_clears_current_estimates(self):
        """Reset removes engine from current_estimates."""
        tracker = _make_tracker()
        tracker.current_estimates["google"] = {
            "base": 2.0,
            "min": 1.0,
            "max": 5.0,
        }

        tracker.reset_engine("google")

        assert "google" not in tracker.current_estimates

    def test_clears_both_memory_structures(self):
        """Reset clears both attempts and estimates simultaneously."""
        tracker = _make_tracker()
        tracker.recent_attempts["google"] = deque(maxlen=100)
        tracker.current_estimates["google"] = {
            "base": 2.0,
            "min": 1.0,
            "max": 5.0,
        }

        tracker.reset_engine("google")

        assert "google" not in tracker.recent_attempts
        assert "google" not in tracker.current_estimates

    def test_does_not_affect_other_engines(self):
        """Reset only clears the specified engine."""
        tracker = _make_tracker()
        tracker.recent_attempts["google"] = deque(
            [{"success": True}], maxlen=100
        )
        tracker.recent_attempts["bing"] = deque([{"success": True}], maxlen=100)
        tracker.current_estimates["google"] = {
            "base": 2.0,
            "min": 1.0,
            "max": 5.0,
        }
        tracker.current_estimates["bing"] = {
            "base": 3.0,
            "min": 1.5,
            "max": 6.0,
        }

        tracker.reset_engine("google")

        assert "google" not in tracker.recent_attempts
        assert "google" not in tracker.current_estimates
        assert "bing" in tracker.recent_attempts
        assert "bing" in tracker.current_estimates

    def test_reset_nonexistent_engine_no_error(self):
        """Resetting an engine with no data does not raise."""
        tracker = _make_tracker()

        tracker.reset_engine("nonexistent")

    def test_reset_idempotent(self):
        """Resetting the same engine twice does not raise."""
        tracker = _make_tracker()
        tracker.current_estimates["google"] = {
            "base": 2.0,
            "min": 1.0,
            "max": 5.0,
        }

        tracker.reset_engine("google")
        tracker.reset_engine("google")  # Should not raise

    def test_reset_only_attempts_present(self):
        """Reset when only recent_attempts exists (no estimate)."""
        tracker = _make_tracker()
        tracker.recent_attempts["google"] = deque(maxlen=100)

        tracker.reset_engine("google")

        assert "google" not in tracker.recent_attempts

    def test_reset_only_estimates_present(self):
        """Reset when only current_estimates exists (no attempts)."""
        tracker = _make_tracker()
        tracker.current_estimates["google"] = {
            "base": 2.0,
            "min": 1.0,
            "max": 5.0,
        }

        tracker.reset_engine("google")

        assert "google" not in tracker.current_estimates


# ── get_search_quality_stats integration with record_outcome ────────


class TestQualityStatsIntegration:
    """Integration tests: record outcomes then check quality stats."""

    def test_record_then_check_quality(self):
        """Record real outcomes and verify quality stats reflect them."""
        tracker = _make_tracker()

        # Record outcomes with search_result_count
        for count in [8, 12, 15]:
            if "test_engine" not in tracker.recent_attempts:
                tracker.recent_attempts["test_engine"] = deque(
                    maxlen=tracker.memory_window
                )
            tracker.recent_attempts["test_engine"].append(
                {
                    "wait_time": 1.0,
                    "success": True,
                    "timestamp": time.time(),
                    "search_result_count": count,
                }
            )

        result = tracker.get_search_quality_stats(engine_type="test_engine")

        assert len(result) == 1
        stat = result[0]
        expected_avg = (8 + 12 + 15) / 3
        assert abs(stat["recent_avg_results"] - expected_avg) < 0.01
        assert stat["min_recent_results"] == 8
        assert stat["max_recent_results"] == 15
        assert stat["status"] == "EXCELLENT"

    def test_mixed_results_and_failures(self):
        """Mixed success/failure outcomes with varying result counts."""
        tracker = _make_tracker()
        _seed_attempts(
            tracker,
            "engine",
            [
                {"search_result_count": 10, "success": True},
                {"search_result_count": 0, "success": False},
                {"search_result_count": 5, "success": True},
            ],
        )

        result = tracker.get_search_quality_stats()

        assert len(result) == 1
        stat = result[0]
        # All 3 have search_result_count
        assert stat["sample_size"] == 3
        assert stat["recent_avg_results"] == 5.0
