"""Tests for voice management (list/save/delete)."""

from __future__ import annotations

import base64
import json

import httpx
import pytest
import respx

from murmr._client import AsyncMurmrClient, MurmrClient
from murmr._errors import MurmrError
from tests.helpers import BASE_URL


class TestAsyncVoiceManagement:
    @respx.mock
    @pytest.mark.asyncio
    async def test_list_voices(self) -> None:
        mock_response = {
            "voices": [
                {
                    "id": "voice_abc123def456",
                    "name": "My Voice",
                    "description": "A warm voice",
                    "language": "en",
                    "language_name": "English",
                    "audio_preview_url": None,
                    "created_at": "2025-01-01T00:00:00Z",
                }
            ],
            "saved_count": 1,
            "saved_limit": 10,
            "total": 1,
        }
        respx.get(f"{BASE_URL}/v1/voices").mock(
            return_value=httpx.Response(200, json=mock_response)
        )

        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            result = await client.voices.list_voices()
            assert len(result.voices) == 1
            assert result.voices[0].name == "My Voice"
            assert result.saved_count == 1
            assert result.saved_limit == 10

    @respx.mock
    @pytest.mark.asyncio
    async def test_list_voices_empty(self) -> None:
        respx.get(f"{BASE_URL}/v1/voices").mock(
            return_value=httpx.Response(
                200, json={"voices": [], "saved_count": 0, "saved_limit": 3, "total": 0}
            )
        )

        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            result = await client.voices.list_voices()
            assert len(result.voices) == 0
            assert result.total == 0

    @respx.mock
    @pytest.mark.asyncio
    async def test_save_voice(self) -> None:
        mock_response = {
            "id": "voice_abc123def456",
            "name": "Test Voice",
            "language": "English",
            "description": "A warm voice",
            "prompt_size_bytes": 1024,
            "created_at": "2025-01-01T00:00:00Z",
            "success": True,
            "has_audio_preview": True,
        }
        respx.post(f"{BASE_URL}/v1/voices").mock(
            return_value=httpx.Response(201, json=mock_response)
        )

        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            audio = b"fake-audio-data"
            result = await client.voices.save(
                name="Test Voice", audio=audio, description="A warm voice"
            )

            assert result.success is True
            assert result.name == "Test Voice"

            request = respx.calls.last.request
            body = json.loads(request.content)
            assert body["name"] == "Test Voice"
            assert body["audio"] == base64.b64encode(audio).decode()
            assert body["description"] == "A warm voice"
            assert body["language"] == "English"

    @pytest.mark.asyncio
    async def test_save_validates_empty_name(self) -> None:
        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            with pytest.raises(MurmrError, match="name is required"):
                await client.voices.save(name="", audio=b"x", description="desc")

    @pytest.mark.asyncio
    async def test_save_validates_long_name(self) -> None:
        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            with pytest.raises(MurmrError, match="50 characters"):
                await client.voices.save(
                    name="a" * 51, audio=b"x", description="desc"
                )

    @pytest.mark.asyncio
    async def test_save_validates_empty_audio(self) -> None:
        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            with pytest.raises(MurmrError, match="audio is required"):
                await client.voices.save(name="Test", audio=b"", description="desc")

    @pytest.mark.asyncio
    async def test_save_validates_empty_description(self) -> None:
        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            with pytest.raises(MurmrError, match="description is required"):
                await client.voices.save(name="Test", audio=b"x", description="")

    @respx.mock
    @pytest.mark.asyncio
    async def test_delete_voice(self) -> None:
        mock_response = {
            "success": True,
            "id": "voice_abc123def456",
            "message": 'Voice "Test" deleted',
        }
        respx.delete(f"{BASE_URL}/v1/voices/voice_abc123def456").mock(
            return_value=httpx.Response(200, json=mock_response)
        )

        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            result = await client.voices.delete("voice_abc123def456")
            assert result.success is True

    @pytest.mark.asyncio
    async def test_delete_validates_id(self) -> None:
        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            with pytest.raises(MurmrError):
                await client.voices.delete("")

    @respx.mock
    @pytest.mark.asyncio
    async def test_extract_embeddings(self) -> None:
        mock_response = {
            "success": True,
            "prompt_data": "base64-encoded-prompt-tensor",
            "prompt_size_bytes": 2048,
        }
        respx.post(f"{BASE_URL}/v1/voices/extract-embeddings").mock(
            return_value=httpx.Response(200, json=mock_response)
        )

        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            audio = b"fake-wav-audio-data"
            result = await client.voices.extract_embeddings(
                audio=audio, ref_text="Hello world"
            )

            assert result.prompt_data == "base64-encoded-prompt-tensor"
            assert result.prompt_size_bytes == 2048

            request = respx.calls.last.request
            body = json.loads(request.content)
            assert body["audio"] == base64.b64encode(audio).decode()
            assert body["ref_text"] == "Hello world"

    @respx.mock
    @pytest.mark.asyncio
    async def test_extract_embeddings_bytearray(self) -> None:
        respx.post(f"{BASE_URL}/v1/voices/extract-embeddings").mock(
            return_value=httpx.Response(
                200,
                json={"prompt_data": "data", "prompt_size_bytes": 100},
            )
        )

        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            result = await client.voices.extract_embeddings(
                audio=bytearray(b"audio-bytes"), ref_text="test"
            )
            assert result.prompt_data == "data"

    @pytest.mark.asyncio
    async def test_extract_embeddings_validates_empty_audio(self) -> None:
        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            with pytest.raises(MurmrError, match="audio is required"):
                await client.voices.extract_embeddings(audio=b"", ref_text="text")

    @pytest.mark.asyncio
    async def test_extract_embeddings_validates_empty_ref_text(self) -> None:
        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            with pytest.raises(MurmrError, match="ref_text is required"):
                await client.voices.extract_embeddings(audio=b"data", ref_text="")

    @respx.mock
    @pytest.mark.asyncio
    async def test_extract_embeddings_api_error(self) -> None:
        respx.post(f"{BASE_URL}/v1/voices/extract-embeddings").mock(
            return_value=httpx.Response(
                200,
                json={"error": "Model not available"},
            )
        )

        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            with pytest.raises(MurmrError, match="Model not available"):
                await client.voices.extract_embeddings(audio=b"data", ref_text="text")

    @respx.mock
    @pytest.mark.asyncio
    async def test_extract_embeddings_generic_error(self) -> None:
        respx.post(f"{BASE_URL}/v1/voices/extract-embeddings").mock(
            return_value=httpx.Response(200, json={})
        )

        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            with pytest.raises(MurmrError, match="Failed to extract embeddings"):
                await client.voices.extract_embeddings(audio=b"data", ref_text="text")


class TestSyncVoiceManagement:
    @respx.mock
    def test_list_voices(self) -> None:
        respx.get(f"{BASE_URL}/v1/voices").mock(
            return_value=httpx.Response(
                200,
                json={"voices": [], "saved_count": 0, "saved_limit": 3, "total": 0},
            )
        )

        with MurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            result = client.voices.list_voices()
            assert result.total == 0

    @respx.mock
    def test_save_voice(self) -> None:
        respx.post(f"{BASE_URL}/v1/voices").mock(
            return_value=httpx.Response(
                201,
                json={
                    "id": "voice_abc",
                    "name": "V",
                    "language": "en",
                    "description": "d",
                    "prompt_size_bytes": 0,
                    "created_at": "",
                    "success": True,
                    "has_audio_preview": False,
                },
            )
        )

        with MurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            result = client.voices.save(name="V", audio=b"data", description="d")
            assert result.success is True

    @respx.mock
    def test_delete_voice(self) -> None:
        respx.delete(f"{BASE_URL}/v1/voices/voice_abc123").mock(
            return_value=httpx.Response(
                200,
                json={"success": True, "id": "voice_abc123", "message": "Deleted"},
            )
        )

        with MurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            result = client.voices.delete("voice_abc123")
            assert result.success is True

    @respx.mock
    def test_extract_embeddings(self) -> None:
        respx.post(f"{BASE_URL}/v1/voices/extract-embeddings").mock(
            return_value=httpx.Response(
                200,
                json={"prompt_data": "tensor-data", "prompt_size_bytes": 512},
            )
        )

        with MurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            result = client.voices.extract_embeddings(
                audio=b"wav-data", ref_text="Hello"
            )
            assert result.prompt_data == "tensor-data"
            assert result.prompt_size_bytes == 512
