"""Test Home Assistant executor util."""

import concurrent.futures
import time
from unittest.mock import patch

import pytest

from homeassistant.util import executor
from homeassistant.util.executor import InterruptibleThreadPoolExecutor


async def test_executor_shutdown_can_interrupt_threads(
    caplog: pytest.LogCaptureFixture,
) -> None:
    """Test that the executor shutdown can interrupt threads."""

    iexecutor = InterruptibleThreadPoolExecutor()

    def _loop_sleep_in_executor():
        while True:
            time.sleep(0.1)

    sleep_futures = []

    for _ in range(100):
        sleep_futures.append(iexecutor.submit(_loop_sleep_in_executor))

    iexecutor.shutdown()

    for future in sleep_futures:
        with pytest.raises((concurrent.futures.CancelledError, SystemExit)):
            future.result()

    assert "is still running at shutdown" in caplog.text
    assert "time.sleep(0.1)" in caplog.text


async def test_executor_shutdown_only_logs_max_attempts(
    caplog: pytest.LogCaptureFixture,
) -> None:
    """Test that the executor shutdown will only log max attempts."""

    iexecutor = InterruptibleThreadPoolExecutor()

    def _loop_sleep_in_executor():
        time.sleep(0.2)

    iexecutor.submit(_loop_sleep_in_executor)

    with patch.object(executor, "EXECUTOR_SHUTDOWN_TIMEOUT", 0.3):
        iexecutor.shutdown()

    assert "time.sleep(0.2)" in caplog.text
    assert "is still running at shutdown" in caplog.text
    iexecutor.shutdown()


async def test_executor_shutdown_does_not_log_shutdown_on_first_attempt(
    caplog: pytest.LogCaptureFixture,
) -> None:
    """Test that the executor shutdown does not log on first attempt."""

    iexecutor = InterruptibleThreadPoolExecutor()

    def _do_nothing():
        return

    for _ in range(5):
        iexecutor.submit(_do_nothing)

    iexecutor.shutdown()

    assert "is still running at shutdown" not in caplog.text


async def test_overall_timeout_reached(caplog: pytest.LogCaptureFixture) -> None:
    """Test that shutdown moves on when the overall timeout is reached."""

    iexecutor = InterruptibleThreadPoolExecutor()

    def _loop_sleep_in_executor():
        time.sleep(1)

    for _ in range(6):
        iexecutor.submit(_loop_sleep_in_executor)

    start = time.monotonic()
    with patch.object(executor, "EXECUTOR_SHUTDOWN_TIMEOUT", 0.5):
        iexecutor.shutdown()
    finish = time.monotonic()

    assert finish - start < 1.2

    iexecutor.shutdown()
