"""Tests for retry handler.

IMPORTANT: These tests verify the CORRECT behavior.
Fix the bugs in retry_handler.py to make these pass.
DO NOT modify these tests.
"""

import pytest
from src.retry_handler import (
    RetryState,
    retry_with_backoff,
    execute_with_circuit_breaker,
)


class TestRetryState:
    def test_initial_state(self):
        state = RetryState()
        assert state.attempts == 0
        assert state.backoff_ms == 100
        assert state.should_retry() is True

    def test_backoff_doubles_on_each_attempt(self):
        """Backoff should double: 100 → 200 → 400."""
        state = RetryState(initial_backoff_ms=100)
        state.record_attempt("error 1")
        assert state.backoff_ms == 200
        state.record_attempt("error 2")
        assert state.backoff_ms == 400

    def test_should_retry_false_after_max(self):
        state = RetryState(max_retries=2)
        state.record_attempt("e1")
        state.record_attempt("e2")
        assert state.should_retry() is False

    def test_reset_clears_state(self):
        state = RetryState()
        state.record_attempt("error")
        state.reset()
        assert state.attempts == 0
        assert state.backoff_ms == 100


class TestRetryWithBackoff:
    def test_success_on_first_try(self):
        result = retry_with_backoff(lambda: 42)
        assert result["success"] is True
        assert result["result"] == 42
        assert result["attempts"] == 1

    def test_success_after_retries(self):
        call_count = 0

        def flaky():
            nonlocal call_count
            call_count += 1
            if call_count < 3:
                raise ValueError("not yet")
            return "ok"

        result = retry_with_backoff(flaky, max_retries=3)
        assert result["success"] is True
        assert result["result"] == "ok"
        assert result["attempts"] == 3

    def test_all_retries_exhausted(self):
        def always_fail():
            raise RuntimeError("fail")

        result = retry_with_backoff(always_fail, max_retries=2)
        assert result["success"] is False
        assert result["attempts"] == 2

    def test_backoff_increases_across_retries(self):
        """Final backoff should reflect exponential increase."""
        def always_fail():
            raise RuntimeError("fail")

        result = retry_with_backoff(always_fail, max_retries=3)
        assert result["success"] is False
        # After 3 attempts: 100 → 200 → 400
        assert result["final_backoff_ms"] == 400


class TestCircuitBreaker:
    def test_all_succeed(self):
        ops = [lambda: 1, lambda: 2, lambda: 3]
        results = execute_with_circuit_breaker(ops)
        assert len(results) == 3
        assert all(r["success"] for r in results)

    def test_consecutive_failures_trip_breaker(self):
        """Two consecutive failures should trip the circuit breaker."""
        def fail1():
            raise RuntimeError("fail 1")

        def fail2():
            raise RuntimeError("fail 2")

        ops = [fail1, fail2, lambda: 3]
        results = execute_with_circuit_breaker(ops, failure_threshold=2)
        assert results[0]["success"] is False
        assert results[1]["success"] is False
        assert results[2].get("skipped") is True

    def test_non_consecutive_failures_dont_trip(self):
        """Non-consecutive failures should NOT trip the breaker."""
        def make_op(val):
            def op():
                if isinstance(val, Exception):
                    raise val
                return val
            return op

        call_results = [RuntimeError("fail"), 2, RuntimeError("fail"), 4]
        ops = [make_op(v) for v in call_results]
        results = execute_with_circuit_breaker(ops, failure_threshold=2)

        # fail, success (resets), fail (count=1), success
        assert results[0]["success"] is False
        assert results[1]["success"] is True
        assert results[2]["success"] is False
        assert results[3]["success"] is True  # Should NOT be skipped

    def test_empty_operations(self):
        results = execute_with_circuit_breaker([])
        assert results == []
