"""Tests for the Mastodon services."""

from datetime import timedelta
from pathlib import Path
from unittest.mock import AsyncMock, Mock, patch

from mastodon.Mastodon import (
    MastodonAPIError,
    MastodonNotFoundError,
    MastodonUnauthorizedError,
    MediaAttachment,
)
import pytest
from syrupy.assertion import SnapshotAssertion

from homeassistant.components import camera, image, media_source
from homeassistant.components.mastodon.const import (
    ATTR_ACCOUNT_NAME,
    ATTR_ATTRIBUTION_DOMAINS,
    ATTR_AVATAR,
    ATTR_AVATAR_MIME_TYPE,
    ATTR_BOT,
    ATTR_CONTENT_WARNING,
    ATTR_DISCOVERABLE,
    ATTR_DISPLAY_NAME,
    ATTR_DURATION,
    ATTR_FIELDS,
    ATTR_HEADER,
    ATTR_HEADER_MIME_TYPE,
    ATTR_HIDE_NOTIFICATIONS,
    ATTR_IDEMPOTENCY_KEY,
    ATTR_LANGUAGE,
    ATTR_LOCKED,
    ATTR_MEDIA,
    ATTR_MEDIA_DESCRIPTION,
    ATTR_NOTE,
    ATTR_QUOTE_APPROVAL_POLICY,
    ATTR_STATUS,
    ATTR_VISIBILITY,
    DOMAIN,
)
from homeassistant.components.mastodon.services import (
    SERVICE_GET_ACCOUNT,
    SERVICE_MUTE_ACCOUNT,
    SERVICE_POST,
    SERVICE_UNMUTE_ACCOUNT,
    SERVICE_UPDATE_PROFILE,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_CONFIG_ENTRY_ID
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.setup import async_setup_component

from . import setup_integration

from tests.common import MockConfigEntry


async def test_get_account_success(
    hass: HomeAssistant,
    mock_mastodon_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
    snapshot: SnapshotAssertion,
) -> None:
    """Test the get_account service successfully returns account data."""
    await setup_integration(hass, mock_config_entry)

    response = await hass.services.async_call(
        DOMAIN,
        SERVICE_GET_ACCOUNT,
        {
            ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
            ATTR_ACCOUNT_NAME: "@trwnh@mastodon.social",
        },
        blocking=True,
        return_response=True,
    )

    assert response == snapshot
    mock_mastodon_client.account_lookup.assert_called_once_with(
        acct="@trwnh@mastodon.social"
    )


async def test_get_account_failure(
    hass: HomeAssistant,
    mock_mastodon_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test the get_account service handles API errors."""
    await setup_integration(hass, mock_config_entry)

    # Test API error (this is the only error type currently caught by the service)
    mock_mastodon_client.account_lookup.side_effect = MastodonAPIError("API error")
    with pytest.raises(
        HomeAssistantError,
        match='Unable to get account "@test@mastodon.social"',
    ):
        await hass.services.async_call(
            DOMAIN,
            SERVICE_GET_ACCOUNT,
            {
                ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
                ATTR_ACCOUNT_NAME: "@test@mastodon.social",
            },
            blocking=True,
            return_response=True,
        )


@pytest.mark.parametrize(
    (
        "service_data",
        "expected_notifications",
        "expected_duration",
    ),
    [
        (
            {ATTR_ACCOUNT_NAME: "@trwnh@mastodon.social"},
            True,
            None,
        ),
        (
            {
                ATTR_ACCOUNT_NAME: "@trwnh@mastodon.social",
                ATTR_HIDE_NOTIFICATIONS: False,
            },
            False,
            None,
        ),
        (
            {
                ATTR_ACCOUNT_NAME: "@trwnh@mastodon.social",
                ATTR_DURATION: timedelta(hours=2),
            },
            True,
            7200,
        ),
        (
            {
                ATTR_ACCOUNT_NAME: "@trwnh@mastodon.social",
                ATTR_DURATION: timedelta(hours=12),
                ATTR_HIDE_NOTIFICATIONS: False,
            },
            False,
            43200,
        ),
    ],
)
async def test_mute_account_success(
    hass: HomeAssistant,
    mock_mastodon_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
    snapshot: SnapshotAssertion,
    service_data: dict[str, str | int | bool],
    expected_notifications: bool,
    expected_duration: int | None,
) -> None:
    """Test the mute_account service mutes the target account with all options."""
    await setup_integration(hass, mock_config_entry)

    await hass.services.async_call(
        DOMAIN,
        SERVICE_MUTE_ACCOUNT,
        {ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id} | service_data,
        blocking=True,
        return_response=False,
    )

    mock_mastodon_client.account_lookup.assert_called_once_with(
        acct=service_data[ATTR_ACCOUNT_NAME]
    )
    account = mock_mastodon_client.account_lookup.return_value
    assert mock_mastodon_client.account_mute.call_count == 1
    call_args, call_kwargs = mock_mastodon_client.account_mute.call_args

    if call_kwargs:
        actual_id = call_kwargs["id"]
        actual_notifications = call_kwargs["notifications"]
        actual_duration = call_kwargs.get("duration")
    else:
        _, positional_args, _ = call_args
        actual_id, actual_notifications, actual_duration = positional_args

    assert actual_id == account.id
    assert actual_notifications == expected_notifications
    assert actual_duration == expected_duration


async def test_mute_account_duration_too_long(
    hass: HomeAssistant,
    mock_mastodon_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test mute_account rejects overly long durations."""
    await setup_integration(hass, mock_config_entry)

    with (
        patch("homeassistant.components.mastodon.services.MAX_DURATION_SECONDS", 5),
        pytest.raises(ServiceValidationError) as err,
    ):
        await hass.services.async_call(
            DOMAIN,
            SERVICE_MUTE_ACCOUNT,
            {
                ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
                ATTR_ACCOUNT_NAME: "@trwnh@mastodon.social",
                ATTR_DURATION: timedelta(seconds=10),
            },
            blocking=True,
            return_response=False,
        )

    assert err.value.translation_key == "mute_duration_too_long"
    mock_mastodon_client.account_mute.assert_not_called()


async def test_mute_account_failure_not_found(
    hass: HomeAssistant,
    mock_mastodon_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test mute_account raises validation when account does not exist."""
    await setup_integration(hass, mock_config_entry)

    mock_mastodon_client.account_lookup.side_effect = MastodonNotFoundError(
        "account not found"
    )

    with pytest.raises(ServiceValidationError):
        await hass.services.async_call(
            DOMAIN,
            SERVICE_MUTE_ACCOUNT,
            {
                ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
                ATTR_ACCOUNT_NAME: "@trwnh@mastodon.social",
            },
            blocking=True,
            return_response=False,
        )

    mock_mastodon_client.account_lookup.assert_called_once_with(
        acct="@trwnh@mastodon.social"
    )
    mock_mastodon_client.account_mute.assert_not_called()


async def test_mute_account_failure_api_error(
    hass: HomeAssistant,
    mock_mastodon_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test mute_account wraps API errors with translated message."""
    await setup_integration(hass, mock_config_entry)

    mock_mastodon_client.account_mute.side_effect = MastodonAPIError("mute failed")

    with pytest.raises(
        HomeAssistantError,
        match='Unable to mute account "@trwnh@mastodon.social"',
    ):
        await hass.services.async_call(
            DOMAIN,
            SERVICE_MUTE_ACCOUNT,
            {
                ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
                ATTR_ACCOUNT_NAME: "@trwnh@mastodon.social",
            },
            blocking=True,
            return_response=False,
        )

    mock_mastodon_client.account_lookup.assert_called_once_with(
        acct="@trwnh@mastodon.social"
    )
    account = mock_mastodon_client.account_lookup.return_value
    mock_mastodon_client.account_mute.assert_called_once_with(
        id=account.id, notifications=True, duration=None
    )


async def test_unmute_account_success(
    hass: HomeAssistant,
    mock_mastodon_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test the unmute_account service unmutes the target account."""
    await setup_integration(hass, mock_config_entry)

    await hass.services.async_call(
        DOMAIN,
        SERVICE_UNMUTE_ACCOUNT,
        {
            ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
            ATTR_ACCOUNT_NAME: "@trwnh@mastodon.social",
        },
        blocking=True,
        return_response=False,
    )

    mock_mastodon_client.account_lookup.assert_called_once_with(
        acct="@trwnh@mastodon.social"
    )
    account = mock_mastodon_client.account_lookup.return_value
    mock_mastodon_client.account_unmute.assert_called_once_with(id=account.id)


async def test_unmute_account_failure_not_found(
    hass: HomeAssistant,
    mock_mastodon_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test unmute_account raises validation when account does not exist."""
    await setup_integration(hass, mock_config_entry)

    mock_mastodon_client.account_lookup.side_effect = MastodonNotFoundError(
        "account not found"
    )

    with pytest.raises(ServiceValidationError):
        await hass.services.async_call(
            DOMAIN,
            SERVICE_UNMUTE_ACCOUNT,
            {
                ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
                ATTR_ACCOUNT_NAME: "@trwnh@mastodon.social",
            },
            blocking=True,
            return_response=False,
        )

    mock_mastodon_client.account_lookup.assert_called_once_with(
        acct="@trwnh@mastodon.social"
    )
    mock_mastodon_client.account_unmute.assert_not_called()


async def test_unmute_account_failure_api_error(
    hass: HomeAssistant,
    mock_mastodon_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test unmute_account wraps API errors with translated message."""
    await setup_integration(hass, mock_config_entry)

    mock_mastodon_client.account_unmute.side_effect = MastodonAPIError("unmute failed")

    with pytest.raises(
        HomeAssistantError,
        match='Unable to unmute account "@trwnh@mastodon.social"',
    ):
        await hass.services.async_call(
            DOMAIN,
            SERVICE_UNMUTE_ACCOUNT,
            {
                ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
                ATTR_ACCOUNT_NAME: "@trwnh@mastodon.social",
            },
            blocking=True,
            return_response=False,
        )

    mock_mastodon_client.account_lookup.assert_called_once_with(
        acct="@trwnh@mastodon.social"
    )
    account = mock_mastodon_client.account_lookup.return_value
    mock_mastodon_client.account_unmute.assert_called_once_with(id=account.id)


@pytest.mark.parametrize(
    ("payload", "kwargs"),
    [
        (
            {
                ATTR_STATUS: "test toot",
            },
            {
                "status": "test toot",
                "spoiler_text": None,
                "visibility": None,
                "quote_approval_policy": None,
                "idempotency_key": None,
                "language": None,
                "media_ids": None,
                "sensitive": None,
            },
        ),
        (
            {ATTR_STATUS: "test toot", ATTR_VISIBILITY: "private"},
            {
                "status": "test toot",
                "spoiler_text": None,
                "visibility": "private",
                "quote_approval_policy": None,
                "idempotency_key": None,
                "language": None,
                "media_ids": None,
                "sensitive": None,
            },
        ),
        (
            {
                ATTR_STATUS: "test toot",
                ATTR_CONTENT_WARNING: "Spoiler",
                ATTR_VISIBILITY: "private",
            },
            {
                "status": "test toot",
                "spoiler_text": "Spoiler",
                "visibility": "private",
                "quote_approval_policy": None,
                "idempotency_key": None,
                "language": None,
                "media_ids": None,
                "sensitive": None,
            },
        ),
        (
            {
                ATTR_STATUS: "test toot",
                ATTR_CONTENT_WARNING: "Spoiler",
                ATTR_LANGUAGE: "nl",
                ATTR_MEDIA: "/image.jpg",
            },
            {
                "status": "test toot",
                "spoiler_text": "Spoiler",
                "visibility": None,
                "quote_approval_policy": None,
                "idempotency_key": None,
                "language": "nl",
                "media_ids": "1",
                "sensitive": None,
            },
        ),
        (
            {
                ATTR_STATUS: "test toot",
                ATTR_CONTENT_WARNING: "Spoiler",
                ATTR_LANGUAGE: "en",
                ATTR_MEDIA: "/image.jpg",
                ATTR_MEDIA_DESCRIPTION: "A test image",
            },
            {
                "status": "test toot",
                "spoiler_text": "Spoiler",
                "visibility": None,
                "quote_approval_policy": None,
                "idempotency_key": None,
                "language": "en",
                "media_ids": "1",
                "sensitive": None,
            },
        ),
        (
            {ATTR_STATUS: "test toot", ATTR_LANGUAGE: "invalid-lang"},
            {
                "status": "test toot",
                "language": "invalid-lang",
                "spoiler_text": None,
                "visibility": None,
                "quote_approval_policy": None,
                "idempotency_key": None,
                "media_ids": None,
                "sensitive": None,
            },
        ),
        (
            {
                ATTR_STATUS: "test toot\nwith idempotency",
                ATTR_IDEMPOTENCY_KEY: "post_once_only",
            },
            {
                "status": "test toot\nwith idempotency",
                "idempotency_key": "post_once_only",
                "language": None,
                "spoiler_text": None,
                "visibility": None,
                "quote_approval_policy": None,
                "media_ids": None,
                "sensitive": None,
            },
        ),
        (
            {ATTR_STATUS: "test toot", ATTR_QUOTE_APPROVAL_POLICY: "followers"},
            {
                "status": "test toot",
                "spoiler_text": None,
                "visibility": None,
                "quote_approval_policy": "followers",
                "idempotency_key": None,
                "language": None,
                "media_ids": None,
                "sensitive": None,
            },
        ),
    ],
)
async def test_service_post(
    hass: HomeAssistant,
    mock_mastodon_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
    payload: dict[str, str],
    kwargs: dict[str, str | None],
) -> None:
    """Test the post service."""

    await setup_integration(hass, mock_config_entry)

    with (
        patch.object(hass.config, "is_allowed_path", return_value=True),
        patch.object(
            mock_mastodon_client, "media_post", return_value=MediaAttachment(id="1")
        ),
    ):
        await hass.services.async_call(
            DOMAIN,
            SERVICE_POST,
            {
                ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
            }
            | payload,
            blocking=True,
            return_response=False,
        )

    mock_mastodon_client.status_post.assert_called_with(**kwargs)

    mock_mastodon_client.status_post.reset_mock()


@pytest.mark.parametrize(
    ("payload", "kwargs"),
    [
        (
            {
                ATTR_STATUS: "test toot",
            },
            {"status": "test toot", "spoiler_text": None, "visibility": None},
        ),
        (
            {
                ATTR_STATUS: "test toot",
                ATTR_CONTENT_WARNING: "Spoiler",
                ATTR_MEDIA: "/image.jpg",
            },
            {
                "status": "test toot",
                "spoiler_text": "Spoiler",
                "visibility": None,
                "quote_approval_policy": None,
                "idempotency_key": None,
                "media_ids": "1",
                "media_description": None,
                "sensitive": None,
            },
        ),
    ],
)
async def test_post_service_failed(
    hass: HomeAssistant,
    mock_mastodon_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
    payload: dict[str, str],
    kwargs: dict[str, str | None],
) -> None:
    """Test the post service raising an error."""
    mock_config_entry.add_to_hass(hass)
    await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    hass.config.is_allowed_path = Mock(return_value=True)
    mock_mastodon_client.media_post.return_value = MediaAttachment(id="1")

    mock_mastodon_client.status_post.side_effect = MastodonAPIError

    with pytest.raises(HomeAssistantError, match="Unable to send message"):
        await hass.services.async_call(
            DOMAIN,
            SERVICE_POST,
            {ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id} | payload,
            blocking=True,
            return_response=False,
        )


async def test_post_media_upload_failed(
    hass: HomeAssistant,
    mock_mastodon_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test the post service raising an error because media upload fails."""
    mock_config_entry.add_to_hass(hass)
    await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    payload = {"status": "test toot", "media": "/fail.jpg"}

    mock_mastodon_client.media_post.side_effect = MastodonAPIError

    with (
        patch.object(hass.config, "is_allowed_path", return_value=True),
        pytest.raises(HomeAssistantError, match="Unable to upload image /fail.jpg"),
    ):
        await hass.services.async_call(
            DOMAIN,
            SERVICE_POST,
            {ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id} | payload,
            blocking=True,
            return_response=False,
        )


async def test_post_path_not_whitelisted(
    hass: HomeAssistant,
    mock_mastodon_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test post service error when file path is not whitelisted."""
    mock_config_entry.add_to_hass(hass)
    await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    payload = {"status": "test toot", "media": "/fail.jpg"}

    with pytest.raises(
        HomeAssistantError, match="/fail.jpg is not a whitelisted directory"
    ):
        await hass.services.async_call(
            DOMAIN,
            SERVICE_POST,
            {ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id} | payload,
            blocking=True,
            return_response=False,
        )


async def test_idempotency_key_too_short(
    hass: HomeAssistant,
    mock_mastodon_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test post service error when idempotency key is too short."""
    mock_config_entry.add_to_hass(hass)
    await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    payload = {"status": "test toot", "idempotency_key": "abc"}

    with pytest.raises(
        ServiceValidationError,
        match="Idempotency key must be at least 4 characters long",
    ):
        await hass.services.async_call(
            DOMAIN,
            SERVICE_POST,
            {ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id} | payload,
            blocking=True,
            return_response=False,
        )


async def test_service_entry_availability(
    hass: HomeAssistant,
    mock_mastodon_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test the services without valid entry."""
    mock_config_entry.add_to_hass(hass)
    mock_config_entry2 = MockConfigEntry(domain=DOMAIN)
    mock_config_entry2.add_to_hass(hass)
    await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

    payload = {"status": "test toot"}

    with pytest.raises(ServiceValidationError) as err:
        await hass.services.async_call(
            DOMAIN,
            SERVICE_POST,
            {ATTR_CONFIG_ENTRY_ID: mock_config_entry2.entry_id} | payload,
            blocking=True,
            return_response=False,
        )
    assert err.value.translation_key == "service_config_entry_not_loaded"

    with pytest.raises(ServiceValidationError) as err:
        await hass.services.async_call(
            DOMAIN,
            SERVICE_POST,
            {ATTR_CONFIG_ENTRY_ID: "bad-config_id"} | payload,
            blocking=True,
            return_response=False,
        )
    assert err.value.translation_key == "service_config_entry_not_found"


@pytest.mark.parametrize(
    ("payload", "kwargs"),
    [
        (
            {ATTR_DISPLAY_NAME: "Test User"},
            {ATTR_DISPLAY_NAME: "Test User"},
        ),
        (
            {ATTR_NOTE: "bio"},
            {ATTR_NOTE: "bio"},
        ),
        (
            {ATTR_LOCKED: True},
            {ATTR_LOCKED: True},
        ),
        (
            {ATTR_BOT: False},
            {ATTR_BOT: False},
        ),
        (
            {ATTR_DISCOVERABLE: True},
            {ATTR_DISCOVERABLE: True},
        ),
        (
            {ATTR_FIELDS: [{"name": "Pronouns", "value": "He/Him, They/Them"}]},
            {ATTR_FIELDS: [("Pronouns", "He/Him, They/Them")]},
        ),
        (
            {ATTR_ATTRIBUTION_DOMAINS: ["example.com", "test.com"]},
            {ATTR_ATTRIBUTION_DOMAINS: ["example.com", "test.com"]},
        ),
        (
            {
                ATTR_AVATAR: {
                    "media_content_id": "media-source://camera/camera.demo_camera",
                    "media_content_type": "image/jpeg",
                }
            },
            {ATTR_AVATAR: b"I play the sax\n", ATTR_AVATAR_MIME_TYPE: "image/jpeg"},
        ),
        (
            {
                ATTR_AVATAR: {
                    "media_content_id": "media-source://media_source/local/screenshot.jpg",
                    "media_content_type": "image/png",
                }
            },
            {
                ATTR_AVATAR: Path(
                    "tests/testing_config/media/screenshot.jpg"
                ).resolve(),
                ATTR_AVATAR_MIME_TYPE: "image/jpeg",
            },
        ),
        (
            {
                ATTR_AVATAR: {
                    "media_content_id": "media-source://image/image.test",
                    "media_content_type": "image/png",
                }
            },
            {ATTR_AVATAR: b"\x89PNG", ATTR_AVATAR_MIME_TYPE: "image/png"},
        ),
        (
            {
                ATTR_HEADER: {
                    "media_content_id": "media-source://camera/camera.demo_camera",
                    "media_content_type": "image/jpeg",
                }
            },
            {ATTR_HEADER: b"I play the sax\n", ATTR_HEADER_MIME_TYPE: "image/jpeg"},
        ),
        (
            {
                ATTR_HEADER: {
                    "media_content_id": "media-source://image/image.test",
                    "media_content_type": "image/png",
                }
            },
            {ATTR_HEADER: b"\x89PNG", ATTR_HEADER_MIME_TYPE: "image/png"},
        ),
        (
            {
                ATTR_HEADER: {
                    "media_content_id": "media-source://media_source/local/screenshot.jpg",
                    "media_content_type": "image/png",
                }
            },
            {
                ATTR_HEADER: Path(
                    "tests/testing_config/media/screenshot.jpg"
                ).resolve(),
                ATTR_HEADER_MIME_TYPE: "image/jpeg",
            },
        ),
    ],
)
@pytest.mark.parametrize("return_response", [True, False])
async def test_service_update_profile(
    hass: HomeAssistant,
    mock_mastodon_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
    payload: dict[str, str],
    kwargs: dict[str, str | None],
    return_response: bool,
) -> None:
    """Test the update profile service."""
    assert await async_setup_component(hass, "media_source", {})
    await setup_integration(hass, mock_config_entry)

    assert mock_config_entry.state is ConfigEntryState.LOADED
    with (
        patch(
            "homeassistant.components.camera.async_get_image",
            return_value=camera.Image("image/jpeg", b"I play the sax\n"),
        ),
        patch(
            "homeassistant.components.image.async_get_image",
            return_value=image.Image(content_type="image/png", content=b"\x89PNG"),
        ),
    ):
        await hass.services.async_call(
            DOMAIN,
            SERVICE_UPDATE_PROFILE,
            {ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id, **payload},
            blocking=True,
            return_response=return_response,
        )

    mock_mastodon_client.account_update_credentials.assert_called_with(**kwargs)


@pytest.mark.parametrize(
    ("exception", "translation_key"),
    [
        (MastodonAPIError, "unable_to_update_profile"),
        (MastodonUnauthorizedError, "auth_failed"),
    ],
)
async def test_service_update_profile_exceptions(
    hass: HomeAssistant,
    mock_mastodon_client: AsyncMock,
    mock_config_entry: MockConfigEntry,
    exception: type[Exception],
    translation_key: str,
) -> None:
    """Test the update profile service exceptions."""
    await setup_integration(hass, mock_config_entry)

    assert mock_config_entry.state is ConfigEntryState.LOADED

    mock_mastodon_client.account_update_credentials.side_effect = exception

    with pytest.raises(HomeAssistantError) as err:
        await hass.services.async_call(
            DOMAIN,
            SERVICE_UPDATE_PROFILE,
            {
                ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
                ATTR_DISPLAY_NAME: "Test User",
            },
            blocking=True,
            return_response=True,
        )

    assert err.value.translation_key == translation_key


@pytest.mark.usefixtures("mock_mastodon_client")
async def test_service_update_profile_media_source_not_supported(
    hass: HomeAssistant,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test the update profile service with unsupported media source."""
    assert await async_setup_component(hass, "tts", {})
    await setup_integration(hass, mock_config_entry)

    assert mock_config_entry.state is ConfigEntryState.LOADED

    with (
        patch(
            "homeassistant.components.mastodon.services.async_resolve_media",
            return_value=media_source.PlayMedia(
                url="/api/tts_proxy/WDyphPCh3sAoO3koDY87ew.mp3",
                mime_type="audio/mpeg",
                path=None,
            ),
        ),
        pytest.raises(HomeAssistantError) as err,
    ):
        await hass.services.async_call(
            DOMAIN,
            SERVICE_UPDATE_PROFILE,
            {
                ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id,
                ATTR_AVATAR: {
                    "media_content_id": "media-source://tts/demo?message=Hello+world%21&language=en",
                    "media_content_type": "audio/mp3",
                },
            },
            blocking=True,
            return_response=True,
        )
    assert err.value.translation_key == "media_source_not_supported"
