"""Tests for resources/speech.py."""

from __future__ import annotations

import json

import httpx
import pytest
import respx

from murmr._client import AsyncMurmrClient, MurmrClient
from murmr._errors import MurmrError
from murmr._types import AsyncJobResponse, SyncAudioResponse
from tests.helpers import BASE_URL, make_sse_body


class TestAsyncSpeechCreate:
    @respx.mock
    @pytest.mark.asyncio
    async def test_create_returns_job(self) -> None:
        respx.post(f"{BASE_URL}/v1/audio/speech").mock(
            return_value=httpx.Response(
                202,
                json={
                    "id": "job_test123",
                    "status": "queued",
                    "created_at": "2025-01-01T00:00:00Z",
                },
            )
        )

        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            job = await client.speech.create(input="Hello", voice="voice_abc")
            assert job.id == "job_test123"
            assert job.status == "queued"

    @respx.mock
    @pytest.mark.asyncio
    async def test_create_sends_text_not_input(self) -> None:
        """The API expects 'text' in the body, not 'input'."""
        respx.post(f"{BASE_URL}/v1/audio/speech").mock(
            return_value=httpx.Response(
                202,
                json={
                    "id": "job_x",
                    "status": "queued",
                    "created_at": "2025-01-01T00:00:00Z",
                },
            )
        )

        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            await client.speech.create(input="Hello world", voice="voice_abc")

        body = json.loads(respx.calls.last.request.content)
        assert "input" in body
        assert body["input"] == "Hello world"
        assert "text" not in body

    @respx.mock
    @pytest.mark.asyncio
    async def test_create_with_voice_clone_prompt(self) -> None:
        respx.post(f"{BASE_URL}/v1/audio/speech").mock(
            return_value=httpx.Response(
                202,
                json={
                    "id": "job_clone",
                    "status": "queued",
                    "created_at": "2025-01-01T00:00:00Z",
                },
            )
        )

        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            await client.speech.create(
                input="Hello",
                voice="voice_abc",
                voice_clone_prompt="base64data",
            )

        body = json.loads(respx.calls.last.request.content)
        assert "voice_clone_prompt" in body
        assert "voice" not in body

    @pytest.mark.asyncio
    async def test_create_validates_empty_voice(self) -> None:
        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            with pytest.raises(MurmrError, match="voice ID is required"):
                await client.speech.create(input="Hello", voice="")

    @pytest.mark.asyncio
    async def test_create_validates_webhook_url(self) -> None:
        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            with pytest.raises(MurmrError, match="webhook_url must use HTTPS"):
                await client.speech.create(
                    input="Hello",
                    voice="voice_abc",
                    webhook_url="http://example.com/hook",
                )


class TestAsyncSpeechCreateSync:
    """Tests for the sync (default) response path — 200 with audio bytes."""

    @respx.mock
    @pytest.mark.asyncio
    async def test_create_returns_sync_audio_on_200(self) -> None:
        audio_bytes = b"\x52\x49\x46\x46" * 100
        respx.post(f"{BASE_URL}/v1/audio/speech").mock(
            return_value=httpx.Response(
                200,
                content=audio_bytes,
                headers={
                    "content-type": "audio/wav",
                    "x-audio-duration-ms": "1500",
                    "x-total-time-ms": "2300",
                },
            )
        )

        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            result = await client.speech.create(input="Hello", voice="voice_abc")

        assert isinstance(result, SyncAudioResponse)
        assert result.audio == audio_bytes
        assert result.content_type == "audio/wav"
        assert result.duration_ms == 1500
        assert result.total_time_ms == 2300

    @respx.mock
    @pytest.mark.asyncio
    async def test_create_returns_async_job_on_202(self) -> None:
        respx.post(f"{BASE_URL}/v1/audio/speech").mock(
            return_value=httpx.Response(
                202,
                json={
                    "id": "job_async",
                    "status": "queued",
                    "created_at": "2026-01-01T00:00:00Z",
                },
            )
        )

        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            result = await client.speech.create(
                input="Hello",
                voice="voice_abc",
                webhook_url="https://example.com/hook",
            )

        assert isinstance(result, AsyncJobResponse)
        assert result.id == "job_async"


class TestSyncSpeechCreateSync:
    """Tests for sync client, sync response path."""

    @respx.mock
    def test_create_returns_sync_audio_on_200(self) -> None:
        audio_bytes = b"\x00\x01\x02\x03" * 50
        respx.post(f"{BASE_URL}/v1/audio/speech").mock(
            return_value=httpx.Response(
                200,
                content=audio_bytes,
                headers={
                    "content-type": "audio/mp3",
                    "x-audio-duration-ms": "500",
                    "x-total-time-ms": "800",
                },
            )
        )

        with MurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            result = client.speech.create(input="Hello", voice="voice_abc")

        assert isinstance(result, SyncAudioResponse)
        assert result.audio == audio_bytes
        assert result.content_type == "audio/mp3"

    @respx.mock
    def test_create_returns_async_job_on_202(self) -> None:
        respx.post(f"{BASE_URL}/v1/audio/speech").mock(
            return_value=httpx.Response(
                202,
                json={
                    "id": "job_sync202",
                    "status": "queued",
                    "created_at": "2026-01-01T00:00:00Z",
                },
            )
        )

        with MurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            result = client.speech.create(
                input="Hello",
                voice="voice_abc",
                webhook_url="https://example.com/hook",
            )

        assert isinstance(result, AsyncJobResponse)
        assert result.id == "job_sync202"


class TestAsyncSpeechStream:
    @respx.mock
    @pytest.mark.asyncio
    async def test_stream_yields_chunks(self) -> None:
        pcm1 = b"\x01\x00" * 50
        pcm2 = b"\x02\x00" * 50
        sse_body = make_sse_body(pcm1, pcm2)

        respx.post(f"{BASE_URL}/v1/audio/speech/stream").mock(
            return_value=httpx.Response(200, content=sse_body)
        )

        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            chunks: list[bytes] = []
            async with client.speech.stream(
                input="Hello", voice="voice_abc"
            ) as stream:
                async for chunk in stream:
                    if chunk.audio_bytes:
                        chunks.append(chunk.audio_bytes)

            assert len(chunks) == 2
            assert chunks[0] == pcm1

    @pytest.mark.asyncio
    async def test_stream_validates_empty_voice(self) -> None:
        async with AsyncMurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            with pytest.raises(MurmrError, match="voice ID is required"):
                async with client.speech.stream(input="Hello", voice="") as _:
                    pass


class TestSyncSpeech:
    @respx.mock
    def test_create_sync(self) -> None:
        respx.post(f"{BASE_URL}/v1/audio/speech").mock(
            return_value=httpx.Response(
                202,
                json={
                    "id": "job_sync",
                    "status": "queued",
                    "created_at": "2025-01-01T00:00:00Z",
                },
            )
        )

        with MurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            job = client.speech.create(input="Hello", voice="voice_abc")
            assert job.id == "job_sync"

    @respx.mock
    def test_stream_sync(self) -> None:
        pcm = b"\x03\x04" * 100
        sse_body = make_sse_body(pcm)

        respx.post(f"{BASE_URL}/v1/audio/speech/stream").mock(
            return_value=httpx.Response(200, content=sse_body)
        )

        with MurmrClient(api_key="test_key", base_url=BASE_URL) as client:
            chunks: list[bytes] = []
            with client.speech.stream(input="Hello", voice="voice_abc") as stream:
                for chunk in stream:
                    if chunk.audio_bytes:
                        chunks.append(chunk.audio_bytes)

            assert len(chunks) == 1
            assert chunks[0] == pcm


class TestClientErrors:
    def test_missing_api_key(self) -> None:
        with pytest.raises(MurmrError, match="api_key is required"):
            MurmrClient(api_key="")

    @pytest.mark.asyncio
    async def test_missing_api_key_async(self) -> None:
        with pytest.raises(MurmrError, match="api_key is required"):
            AsyncMurmrClient(api_key="")

    @respx.mock
    @pytest.mark.asyncio
    async def test_auth_header_sent(self) -> None:
        respx.post(f"{BASE_URL}/v1/audio/speech").mock(
            return_value=httpx.Response(
                202,
                json={
                    "id": "job_auth",
                    "status": "queued",
                    "created_at": "2025-01-01T00:00:00Z",
                },
            )
        )

        async with AsyncMurmrClient(
            api_key="murmr_sk_test_xyz", base_url=BASE_URL
        ) as client:
            await client.speech.create(input="Hello", voice="voice_abc")

        assert (
            respx.calls.last.request.headers["Authorization"]
            == "Bearer murmr_sk_test_xyz"
        )
