import asyncio
import time
from unittest.mock import AsyncMock, Mock

import numpy as np
import pytest
from handsfreed.segmentation.vad import (
    SilentState,
    SpeechState,
    EndingSpeechState,
    AbstractVADState,
)
from handsfreed.ipc_models import CliOutputMode


@pytest.fixture
def strategy_mock():
    """Create a mock VADSegmentationStrategy."""
    strategy = AsyncMock()
    strategy.vad_config.threshold = 0.5
    strategy.vad_config.neg_threshold = 0.3
    strategy.vad_config.max_speech_duration_s = 10.0
    strategy.vad_config.min_silence_duration_ms = 100
    strategy.vad_config.auto_disable_duration_s = 0.0
    strategy._pre_roll_buffer = [np.ones(512)]
    strategy._current_segment = []
    strategy.on_auto_disable = Mock()
    strategy._enabled = True
    return strategy


@pytest.mark.asyncio
async def test_silent_state_speech_detected(strategy_mock):
    """Test SilentState transitions to SpeechState when speech is detected."""
    state = SilentState()
    new_state = await state.handle_vad_result(strategy_mock, 0.7, np.ones(512))
    assert isinstance(new_state, SpeechState)
    assert len(strategy_mock._current_segment) == 1


@pytest.mark.asyncio
async def test_silent_state_no_speech(strategy_mock):
    """Test SilentState remains in SilentState when no speech is detected."""
    state = SilentState()
    new_state = await state.handle_vad_result(strategy_mock, 0.2, np.ones(512))
    assert isinstance(new_state, SilentState)
    assert len(strategy_mock._current_segment) == 0


@pytest.mark.asyncio
async def test_speech_state_no_speech(strategy_mock):
    """Test SpeechState transitions to EndingSpeechState when silence is detected."""
    state = SpeechState()
    new_state = await state.handle_vad_result(strategy_mock, 0.2, np.ones(512))
    assert isinstance(new_state, EndingSpeechState)
    assert len(strategy_mock._current_segment) == 1


@pytest.mark.asyncio
async def test_speech_state_speech_continues(strategy_mock):
    """Test SpeechState remains in SpeechState when speech continues."""
    state = SpeechState()
    new_state = await state.handle_vad_result(strategy_mock, 0.7, np.ones(512))
    assert isinstance(new_state, SpeechState)
    assert len(strategy_mock._current_segment) == 1


@pytest.mark.asyncio
async def test_speech_state_max_duration(strategy_mock):
    """Test SpeechState transitions to SilentState when max speech duration is reached."""
    strategy_mock.vad_config.max_speech_duration_s = 0.1
    state = SpeechState()
    # Add enough frames to exceed max duration
    for _ in range(10):
        strategy_mock._current_segment.append(np.ones(2000))
    new_state = await state.handle_vad_result(strategy_mock, 0.7, np.ones(512))
    assert isinstance(new_state, SilentState)
    strategy_mock._finalize_segment.assert_awaited_once()


@pytest.mark.asyncio
async def test_ending_speech_state_speech_resumes(strategy_mock):
    """Test EndingSpeechState transitions back to SpeechState when speech resumes."""
    state = EndingSpeechState()
    new_state = await state.handle_vad_result(strategy_mock, 0.7, np.ones(512))
    assert isinstance(new_state, SpeechState)
    assert len(strategy_mock._current_segment) == 1


@pytest.mark.asyncio
async def test_ending_speech_state_silence_continues(strategy_mock):
    """Test EndingSpeechState remains in EndingSpeechState when silence continues."""
    state = EndingSpeechState()
    new_state = await state.handle_vad_result(strategy_mock, 0.2, np.ones(512))
    assert isinstance(new_state, EndingSpeechState)
    assert len(strategy_mock._current_segment) == 1


@pytest.mark.asyncio
async def test_ending_speech_state_silence_timeout(strategy_mock):
    """Test EndingSpeechState transitions to SilentState after silence timeout."""
    state = EndingSpeechState()
    await asyncio.sleep(0.2)  # Wait for silence timeout
    new_state = await state.handle_vad_result(strategy_mock, 0.2, np.ones(512))
    assert isinstance(new_state, SilentState)
    strategy_mock._finalize_segment.assert_awaited_once()


@pytest.mark.asyncio
async def test_silent_state_auto_disable(strategy_mock):
    """Test SilentState triggers auto-disable callback after timeout."""
    strategy_mock.vad_config.auto_disable_duration_s = 0.1
    state = SilentState()

    # Initial state, should not trigger yet
    await state.handle_vad_result(strategy_mock, 0.2, np.ones(512))
    strategy_mock.on_auto_disable.assert_not_called()

    # Wait for timeout
    await asyncio.sleep(0.15)

    # Should trigger now
    await state.handle_vad_result(strategy_mock, 0.2, np.ones(512))
    strategy_mock.on_auto_disable.assert_called_once()


@pytest.mark.asyncio
async def test_silent_state_no_auto_disable_when_disabled(strategy_mock):
    """Test SilentState does not trigger auto-disable when config is 0."""
    strategy_mock.vad_config.auto_disable_duration_s = 0.0
    state = SilentState()

    # Initialize timer
    await state.handle_vad_result(strategy_mock, 0.2, np.ones(512))

    await asyncio.sleep(0.1)
    await state.handle_vad_result(strategy_mock, 0.2, np.ones(512))
    strategy_mock.on_auto_disable.assert_not_called()


@pytest.mark.asyncio
async def test_silent_state_no_auto_disable_when_not_enabled(strategy_mock):
    """Test SilentState does not trigger auto-disable when not enabled."""
    strategy_mock.vad_config.auto_disable_duration_s = 0.1
    strategy_mock._enabled = False
    state = SilentState()

    await asyncio.sleep(0.15)
    await state.handle_vad_result(strategy_mock, 0.2, np.ones(512))
    strategy_mock.on_auto_disable.assert_not_called()


@pytest.mark.asyncio
async def test_silent_state_resets_timer(strategy_mock):
    """Test SilentState resets timer after triggering."""
    strategy_mock.vad_config.auto_disable_duration_s = 0.1
    state = SilentState()

    # Initialize timer
    await state.handle_vad_result(strategy_mock, 0.2, np.ones(512))

    # Trigger once
    await asyncio.sleep(0.15)
    await state.handle_vad_result(strategy_mock, 0.2, np.ones(512))
    strategy_mock.on_auto_disable.assert_called_once()

    # Reset mock to test next trigger
    strategy_mock.on_auto_disable.reset_mock()

    # Immediate next call should NOT trigger (timer was reset)
    await state.handle_vad_result(strategy_mock, 0.2, np.ones(512))
    strategy_mock.on_auto_disable.assert_not_called()
