from __future__ import annotations

import asyncio
import unittest

from memnixa.channels.typing import create_reply_indicator_callbacks


class ReplyIndicatorCallbacksTests(unittest.IsolatedAsyncioTestCase):
    async def test_keepalive_restarts_until_idle(self) -> None:
        events: list[str] = []

        async def start() -> None:
            events.append("start")

        async def stop() -> None:
            events.append("stop")

        callbacks = create_reply_indicator_callbacks(
            start=start,
            stop=stop,
            on_start_error=lambda exc: self.fail(str(exc)),
            keepalive_interval_ms=20,
            max_duration_ms=250,
        )

        await callbacks.on_reply_start()
        await asyncio.sleep(0.07)
        if callbacks.on_idle:
            await callbacks.on_idle()

        self.assertGreaterEqual(events.count("start"), 2)
        self.assertEqual(events.count("stop"), 1)

    async def test_stop_is_deduped_across_idle_and_cleanup(self) -> None:
        stop_calls = 0

        async def stop() -> None:
            nonlocal stop_calls
            stop_calls += 1

        callbacks = create_reply_indicator_callbacks(
            start=lambda: asyncio.sleep(0),
            stop=stop,
            on_start_error=lambda exc: self.fail(str(exc)),
            keepalive_interval_ms=0,
            max_duration_ms=0,
        )

        await callbacks.on_reply_start()
        if callbacks.on_idle:
            await callbacks.on_idle()
        if callbacks.on_cleanup:
            await callbacks.on_cleanup()

        self.assertEqual(stop_calls, 1)

    async def test_trips_after_consecutive_start_failures(self) -> None:
        errors: list[str] = []
        attempts = 0

        async def start() -> None:
            nonlocal attempts
            attempts += 1
            raise RuntimeError("boom")

        callbacks = create_reply_indicator_callbacks(
            start=start,
            stop=None,
            on_start_error=lambda exc: errors.append(str(exc)),
            keepalive_interval_ms=10,
            max_consecutive_failures=2,
            max_duration_ms=0,
        )

        await callbacks.on_reply_start()
        await asyncio.sleep(0.05)
        if callbacks.on_cleanup:
            await callbacks.on_cleanup()

        self.assertGreaterEqual(attempts, 2)
        self.assertEqual(len(errors), 2)


if __name__ == "__main__":
    unittest.main()
