"""Test scheduler retry functionality."""

import asyncio
import re

import pytest

from .types import APIClientConnectedFactory, RunCompiledFunction


@pytest.mark.asyncio
async def test_scheduler_retry_test(
    yaml_config: str,
    run_compiled: RunCompiledFunction,
    api_client_connected: APIClientConnectedFactory,
) -> None:
    """Test that scheduler retry functionality works correctly."""
    # Track test progress
    simple_retry_done = asyncio.Event()
    backoff_retry_done = asyncio.Event()
    immediate_done_done = asyncio.Event()
    cancel_retry_done = asyncio.Event()
    empty_name_retry_done = asyncio.Event()
    component_retry_done = asyncio.Event()
    multiple_name_done = asyncio.Event()
    const_char_done = asyncio.Event()
    static_char_done = asyncio.Event()
    test_complete = asyncio.Event()

    # Track retry counts
    simple_retry_count = 0
    backoff_retry_count = 0
    immediate_done_count = 0
    cancel_retry_count = 0
    empty_name_retry_count = 0
    component_retry_count = 0
    multiple_name_count = 0
    const_char_retry_count = 0
    static_char_retry_count = 0

    # Track specific test results
    cancel_result = None
    empty_cancel_result = None
    backoff_intervals = []

    def on_log_line(line: str) -> None:
        nonlocal simple_retry_count, backoff_retry_count, immediate_done_count
        nonlocal cancel_retry_count, empty_name_retry_count, component_retry_count
        nonlocal multiple_name_count, const_char_retry_count, static_char_retry_count
        nonlocal cancel_result, empty_cancel_result

        # Strip ANSI color codes
        clean_line = re.sub(r"\x1b\[[0-9;]*m", "", line)

        # Simple retry test
        if "Simple retry attempt" in clean_line:
            if match := re.search(r"Simple retry attempt (\d+)", clean_line):
                simple_retry_count = int(match.group(1))

        elif "Simple retry succeeded on attempt" in clean_line:
            simple_retry_done.set()

        # Backoff retry test
        elif "Backoff retry attempt" in clean_line:
            if match := re.search(
                r"Backoff retry attempt (\d+).*interval=(\d+)ms", clean_line
            ):
                backoff_retry_count = int(match.group(1))
                interval = int(match.group(2))
                if backoff_retry_count > 1:  # Skip first (immediate) call
                    backoff_intervals.append(interval)

        elif "Backoff retry completed" in clean_line:
            backoff_retry_done.set()

        # Immediate done test
        elif "Immediate done retry called" in clean_line:
            immediate_done_count += 1
            immediate_done_done.set()

        # Cancel retry test
        elif "Cancel test retry attempt" in clean_line:
            cancel_retry_count += 1

        elif "Retry cancellation result:" in clean_line:
            cancel_result = "true" in clean_line
            cancel_retry_done.set()

        # Empty name retry test
        elif "Empty name retry attempt" in clean_line:
            if match := re.search(r"Empty name retry attempt (\d+)", clean_line):
                empty_name_retry_count = int(match.group(1))

        elif "Empty name retry cancel result:" in clean_line:
            empty_cancel_result = "true" in clean_line

        elif "Empty name retry ran" in clean_line:
            empty_name_retry_done.set()

        # Component retry test
        elif "Component retry attempt" in clean_line:
            if match := re.search(r"Component retry attempt (\d+)", clean_line):
                component_retry_count = int(match.group(1))
                if component_retry_count >= 2:
                    component_retry_done.set()

        # Multiple same name test
        elif "Second duplicate retry attempt" in clean_line:
            if match := re.search(r"counter=(\d+)", clean_line):
                multiple_name_count = int(match.group(1))
                if multiple_name_count >= 20:
                    multiple_name_done.set()

        # Const char retry test
        elif "Const char retry" in clean_line:
            if match := re.search(r"Const char retry (\d+)", clean_line):
                const_char_retry_count = int(match.group(1))
                const_char_done.set()

        # Static const char retry test
        elif "Static const char retry" in clean_line:
            if match := re.search(r"Static const char retry (\d+)", clean_line):
                static_char_retry_count = int(match.group(1))
                static_char_done.set()

        elif "Static cancel result:" in clean_line:
            # This is part of test 9, but we don't track it separately
            pass

        # Test completion
        elif "All retry tests completed" in clean_line:
            test_complete.set()

    async with (
        run_compiled(yaml_config, line_callback=on_log_line),
        api_client_connected() as client,
    ):
        # Verify we can connect
        device_info = await client.device_info()
        assert device_info is not None
        assert device_info.name == "scheduler-retry-test"

        # Wait for simple retry test
        try:
            await asyncio.wait_for(simple_retry_done.wait(), timeout=1.0)
        except TimeoutError:
            pytest.fail(
                f"Simple retry test did not complete. Count: {simple_retry_count}"
            )

        assert simple_retry_count == 2, (
            f"Expected 2 simple retry attempts, got {simple_retry_count}"
        )

        # Wait for backoff retry test
        try:
            await asyncio.wait_for(backoff_retry_done.wait(), timeout=3.0)
        except TimeoutError:
            pytest.fail(
                f"Backoff retry test did not complete. Count: {backoff_retry_count}"
            )

        assert backoff_retry_count == 4, (
            f"Expected 4 backoff retry attempts, got {backoff_retry_count}"
        )

        # Verify backoff intervals (allowing for timing variations)
        assert len(backoff_intervals) >= 2, (
            f"Expected at least 2 intervals, got {len(backoff_intervals)}"
        )
        if len(backoff_intervals) >= 3:
            # First interval should be ~50ms (very wide tolerance for heavy system load)
            assert 20 <= backoff_intervals[0] <= 150, (
                f"First interval {backoff_intervals[0]}ms not ~50ms"
            )
            # Second interval should be ~100ms (50ms * 2.0)
            assert 50 <= backoff_intervals[1] <= 250, (
                f"Second interval {backoff_intervals[1]}ms not ~100ms"
            )
            # Third interval should be ~200ms (100ms * 2.0)
            assert 100 <= backoff_intervals[2] <= 500, (
                f"Third interval {backoff_intervals[2]}ms not ~200ms"
            )

        # Wait for immediate done test
        try:
            await asyncio.wait_for(immediate_done_done.wait(), timeout=3.0)
        except TimeoutError:
            pytest.fail(
                f"Immediate done test did not complete. Count: {immediate_done_count}"
            )

        assert immediate_done_count == 1, (
            f"Expected 1 immediate done call, got {immediate_done_count}"
        )

        # Wait for cancel retry test
        try:
            await asyncio.wait_for(cancel_retry_done.wait(), timeout=3.0)
        except TimeoutError:
            pytest.fail(
                f"Cancel retry test did not complete. Count: {cancel_retry_count}"
            )

        assert cancel_result is True, "Retry cancellation should have succeeded"
        assert 2 <= cancel_retry_count <= 5, (
            f"Expected 2-5 cancel retry attempts before cancellation, got {cancel_retry_count}"
        )

        # Wait for empty name retry test
        try:
            await asyncio.wait_for(empty_name_retry_done.wait(), timeout=1.0)
        except TimeoutError:
            pytest.fail(
                f"Empty name retry test did not complete. Count: {empty_name_retry_count}"
            )

        # Empty name retry should run at least once before being cancelled
        assert 1 <= empty_name_retry_count <= 3, (
            f"Expected 1-3 empty name retry attempts, got {empty_name_retry_count}"
        )
        assert empty_cancel_result is True, (
            "Empty name retry cancel should have succeeded"
        )

        # Wait for component retry test
        try:
            await asyncio.wait_for(component_retry_done.wait(), timeout=1.0)
        except TimeoutError:
            pytest.fail(
                f"Component retry test did not complete. Count: {component_retry_count}"
            )

        assert component_retry_count >= 2, (
            f"Expected at least 2 component retry attempts, got {component_retry_count}"
        )

        # Wait for multiple same name test
        try:
            await asyncio.wait_for(multiple_name_done.wait(), timeout=1.0)
        except TimeoutError:
            pytest.fail(
                f"Multiple same name test did not complete. Count: {multiple_name_count}"
            )

        # Should be 20+ (only second retry should run)
        assert multiple_name_count >= 20, (
            f"Expected multiple name count >= 20 (second retry only), got {multiple_name_count}"
        )

        # Wait for const char retry test
        try:
            await asyncio.wait_for(const_char_done.wait(), timeout=1.0)
        except TimeoutError:
            pytest.fail(
                f"Const char retry test did not complete. Count: {const_char_retry_count}"
            )

        assert const_char_retry_count == 1, (
            f"Expected 1 const char retry call, got {const_char_retry_count}"
        )

        # Wait for static char retry test
        try:
            await asyncio.wait_for(static_char_done.wait(), timeout=1.0)
        except TimeoutError:
            pytest.fail(
                f"Static char retry test did not complete. Count: {static_char_retry_count}"
            )

        assert static_char_retry_count == 1, (
            f"Expected 1 static char retry call, got {static_char_retry_count}"
        )

        # Wait for test completion
        try:
            await asyncio.wait_for(test_complete.wait(), timeout=1.0)
        except TimeoutError:
            pytest.fail("Test did not complete within timeout")
