from __future__ import annotations

import asyncio
from typing import Any, cast

from textual.pilot import Pilot

from tests.conftest import build_test_vibe_config
from tests.mock.utils import mock_llm_chunk
from tests.snapshots.base_snapshot_test_app import BaseSnapshotTestApp
from tests.snapshots.snap_compare import SnapCompare
from tests.stubs.fake_audio_player import FakeAudioPlayer
from tests.stubs.fake_backend import FakeBackend
from tests.stubs.fake_tts_client import FakeTTSClient
from vibe.cli.narrator_manager import NarratorManager, NarratorState
import vibe.cli.textual_ui.widgets.narrator_status as narrator_status_mod
from vibe.cli.textual_ui.widgets.narrator_status import NarratorStatus
from vibe.cli.turn_summary import TurnSummaryTracker

narrator_status_mod.SHRINK_FRAMES = "█"
narrator_status_mod.BAR_FRAMES = ["▂▅▇"]
from vibe.core.config import ModelConfig
from vibe.core.tts.tts_client_port import TTSResult
from vibe.core.types import LLMChunk

_TEST_MODEL = ModelConfig(name="test-model", provider="test", alias="test-model")


def _narrator_config():
    return build_test_vibe_config(
        narrator_enabled=True,
        disable_welcome_banner_animation=True,
        displayed_workdir="/test/workdir",
    )


class GatedBackend(FakeBackend):
    def __init__(self, chunks: LLMChunk) -> None:
        super().__init__(chunks)
        self._gate = asyncio.Event()

    def release(self) -> None:
        self._gate.set()

    async def complete(self, **kwargs: Any) -> LLMChunk:
        await self._gate.wait()
        return await super().complete(**kwargs)


class GatedTTSClient(FakeTTSClient):
    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)
        self._gate = asyncio.Event()

    def release(self) -> None:
        self._gate.set()

    async def speak(self, text: str) -> TTSResult:
        await self._gate.wait()
        return await super().speak(text)


class NarratorFlowApp(BaseSnapshotTestApp):
    def __init__(self) -> None:
        self.summary_gate = GatedBackend(
            mock_llm_chunk(content="Summary of the conversation")
        )
        self.tts_gate = GatedTTSClient()
        self.fake_audio_player = FakeAudioPlayer()
        narrator_manager = NarratorManager(
            config_getter=_narrator_config, audio_player=self.fake_audio_player
        )
        # Override turn_summary and tts_client with gated test doubles.
        narrator_manager._turn_summary = TurnSummaryTracker(
            backend=self.summary_gate, model=_TEST_MODEL
        )
        narrator_manager._turn_summary.on_summary = narrator_manager._on_turn_summary
        narrator_manager._tts_client = self.tts_gate
        super().__init__(
            config=_narrator_config(),
            backend=FakeBackend(
                mock_llm_chunk(
                    content="Hello! I can help you.",
                    prompt_tokens=10_000,
                    completion_tokens=2_500,
                )
            ),
            narrator_manager=narrator_manager,
        )


def test_snapshot_narrator_summarizing(snap_compare: SnapCompare) -> None:
    async def run_before(pilot: Pilot) -> None:
        app = cast(NarratorFlowApp, pilot.app)
        # Send message and wait for agent response to complete
        await pilot.press(*"Hello")
        await pilot.press("enter")
        await pilot.pause(0.5)
        # on_turn_end has fired, SUMMARIZING is set, summary backend is gated
        assert app.summary_gate._gate.is_set() is False
        # Freeze animation at frame 0 for deterministic snapshot
        app.query_one(NarratorStatus)._stop_timer()

    assert snap_compare(
        "test_ui_snapshot_narrator_flow.py:NarratorFlowApp",
        terminal_size=(120, 36),
        run_before=run_before,
    )


def test_snapshot_narrator_speaking(snap_compare: SnapCompare) -> None:
    async def run_before(pilot: Pilot) -> None:
        app = cast(NarratorFlowApp, pilot.app)
        await pilot.press(*"Hello")
        await pilot.press("enter")
        await pilot.pause(0.5)
        # Release summary gate → summary resolves → speak task starts → blocks on TTS gate
        app.summary_gate.release()
        await pilot.pause(0.2)
        # Release TTS gate → TTS resolves → SPEAKING set
        app.tts_gate.release()
        await pilot.pause(0.2)
        # Freeze animation at frame 0 for deterministic snapshot
        app.query_one(NarratorStatus)._stop_timer()

    assert snap_compare(
        "test_ui_snapshot_narrator_flow.py:NarratorFlowApp",
        terminal_size=(120, 36),
        run_before=run_before,
    )


def test_snapshot_narrator_idle_after_speaking(snap_compare: SnapCompare) -> None:
    async def run_before(pilot: Pilot) -> None:
        app = cast(NarratorFlowApp, pilot.app)
        await pilot.press(*"Hello")
        await pilot.press("enter")
        await pilot.pause(0.5)
        # Release both gates to reach SPEAKING
        app.summary_gate.release()
        await pilot.pause(0.2)
        app.tts_gate.release()
        await pilot.pause(0.2)
        # Simulate playback finishing (same thread, so call directly)
        app.fake_audio_player.stop()
        narrator = cast(NarratorManager, app._narrator_manager)
        narrator._set_state(NarratorState.IDLE)
        await pilot.pause(0.2)

    assert snap_compare(
        "test_ui_snapshot_narrator_flow.py:NarratorFlowApp",
        terminal_size=(120, 36),
        run_before=run_before,
    )
