"""Common fixtures for the ElevenLabs text-to-speech tests."""

from collections.abc import Generator
from typing import Any
from unittest.mock import AsyncMock, patch

from elevenlabs.core import ApiError
from elevenlabs.types import GetVoicesResponse
from httpx import ConnectError
import pytest

from homeassistant.components.elevenlabs.const import (
    CONF_STT_AUTO_LANGUAGE,
    CONF_STT_MODEL,
    CONF_VOICE,
    DEFAULT_SIMILARITY,
    DOMAIN,
)
from homeassistant.const import CONF_API_KEY, CONF_MODEL
from homeassistant.core import HomeAssistant

from .const import MOCK_MODELS, MOCK_VOICES

from tests.common import MockConfigEntry


@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
    """Override async_setup_entry."""
    with patch(
        "homeassistant.components.elevenlabs.async_setup_entry", return_value=True
    ) as mock_setup_entry:
        yield mock_setup_entry


def _client_mock():
    client_mock = AsyncMock()
    client_mock.voices.get_all.return_value = GetVoicesResponse(voices=MOCK_VOICES)
    client_mock.models.list.return_value = MOCK_MODELS

    return client_mock


@pytest.fixture
def mock_async_client() -> Generator[AsyncMock]:
    """Override async ElevenLabs client."""
    with (
        patch(
            "homeassistant.components.elevenlabs.AsyncElevenLabs",
            return_value=_client_mock(),
        ) as mock_async_client,
        patch(
            "homeassistant.components.elevenlabs.config_flow.AsyncElevenLabs",
            new=mock_async_client,
        ),
        patch(
            "homeassistant.components.elevenlabs.tts.AsyncElevenLabs",
            new=mock_async_client,
        ),
    ):
        yield mock_async_client


@pytest.fixture
def mock_async_client_api_error() -> Generator[AsyncMock]:
    """Override async ElevenLabs client with ApiError side effect."""
    client_mock = _client_mock()
    api_error = ApiError()
    api_error.body = {
        "detail": {"status": "invalid_api_key", "message": "API key is invalid"}
    }
    client_mock.models.list.side_effect = api_error
    client_mock.voices.get_all.side_effect = api_error

    with (
        patch(
            "homeassistant.components.elevenlabs.AsyncElevenLabs",
            return_value=client_mock,
        ) as mock_async_client,
        patch(
            "homeassistant.components.elevenlabs.config_flow.AsyncElevenLabs",
            new=mock_async_client,
        ),
    ):
        yield mock_async_client


@pytest.fixture
def mock_async_client_voices_error() -> Generator[AsyncMock]:
    """Override async ElevenLabs client with ApiError side effect."""
    client_mock = _client_mock()
    api_error = ApiError()
    api_error.body = {
        "detail": {
            "status": "voices_unauthorized",
            "message": "API is unauthorized for voices",
        }
    }
    client_mock.voices.get_all.side_effect = api_error

    with patch(
        "homeassistant.components.elevenlabs.config_flow.AsyncElevenLabs",
        return_value=client_mock,
    ) as mock_async_client:
        yield mock_async_client


@pytest.fixture
def mock_async_client_models_error() -> Generator[AsyncMock]:
    """Override async ElevenLabs client with ApiError side effect."""
    client_mock = _client_mock()
    api_error = ApiError()
    api_error.body = {
        "detail": {
            "status": "models_unauthorized",
            "message": "API is unauthorized for models",
        }
    }
    client_mock.models.list.side_effect = api_error

    with patch(
        "homeassistant.components.elevenlabs.config_flow.AsyncElevenLabs",
        return_value=client_mock,
    ) as mock_async_client:
        yield mock_async_client


@pytest.fixture
def mock_async_client_connect_error() -> Generator[AsyncMock]:
    """Override async ElevenLabs client."""
    client_mock = _client_mock()
    client_mock.models.list.side_effect = ConnectError("Unknown")
    client_mock.voices.get_all.side_effect = ConnectError("Unknown")
    with (
        patch(
            "homeassistant.components.elevenlabs.AsyncElevenLabs",
            return_value=client_mock,
        ) as mock_async_client,
        patch(
            "homeassistant.components.elevenlabs.config_flow.AsyncElevenLabs",
            new=mock_async_client,
        ),
    ):
        yield mock_async_client


async def mock_config_entry_setup(
    hass: HomeAssistant, config_data: dict[str, Any], config_options: dict[str, Any]
) -> None:
    """Mock config entry setup."""
    default_config_data = {
        CONF_API_KEY: "api_key",
    }
    default_config_options = {
        CONF_VOICE: "voice1",
        CONF_MODEL: "model1",
        CONF_STT_MODEL: "stt_model1",
        CONF_STT_AUTO_LANGUAGE: False,
    }
    config_entry = MockConfigEntry(
        domain=DOMAIN,
        data=default_config_data | config_data,
        options=default_config_options | config_options,
    )
    config_entry.add_to_hass(hass)
    client_mock = AsyncMock()
    client_mock.voices.get_all.return_value = GetVoicesResponse(voices=MOCK_VOICES)
    client_mock.models.list.return_value = MOCK_MODELS
    stt_mock = AsyncMock()
    stt_mock.convert.return_value = AsyncMock(
        text="hello world", language_code="en", language_probability=0.95
    )
    client_mock.speech_to_text = stt_mock
    with patch(
        "homeassistant.components.elevenlabs.AsyncElevenLabs", return_value=client_mock
    ):
        assert await hass.config_entries.async_setup(config_entry.entry_id)


@pytest.fixture(name="setup")
async def setup_fixture(
    hass: HomeAssistant,
    config_data: dict[str, Any],
    config_options: dict[str, Any],
    mock_async_client: AsyncMock,
) -> AsyncMock:
    """Set up the test environment."""

    await mock_config_entry_setup(hass, config_data, config_options)

    await hass.async_block_till_done()

    return mock_async_client


@pytest.fixture
def mock_entry() -> MockConfigEntry:
    """Mock a config entry."""
    entry = MockConfigEntry(
        domain="elevenlabs",
        data={
            CONF_API_KEY: "api_key",
        },
        options={
            CONF_MODEL: "model1",
            CONF_VOICE: "voice1",
            CONF_STT_MODEL: "stt_model1",
            CONF_STT_AUTO_LANGUAGE: False,
        },
    )
    entry.models = {
        "model1": "model1",
    }

    entry.voices = {"voice1": "voice1"}
    entry.stt_models = {"stt_model1": "stt_model1"}
    return entry


@pytest.fixture(name="config_data")
def config_data_fixture() -> dict[str, Any]:
    """Return config data."""
    return {}


@pytest.fixture(name="config_options")
def config_options_fixture() -> dict[str, Any]:
    """Return config options."""
    return {}


@pytest.fixture
def mock_similarity():
    """Mock similarity."""
    return DEFAULT_SIMILARITY / 2
