"""Define services for the Overseerr integration."""

import ast
from dataclasses import asdict
from typing import Any, Literal, cast

from python_overseerr import OverseerrClient, OverseerrConnectionError
import voluptuous as vol

from homeassistant.const import ATTR_CONFIG_ENTRY_ID
from homeassistant.core import (
    HomeAssistant,
    ServiceCall,
    ServiceResponse,
    SupportsResponse,
    callback,
)
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import service
from homeassistant.util.json import JsonValueType

from .const import (
    ATTR_MEDIA_ID,
    ATTR_MEDIA_TYPE,
    ATTR_QUERY,
    ATTR_REQUESTED_BY,
    ATTR_SEASONS,
    ATTR_SORT_ORDER,
    ATTR_STATUS,
    DOMAIN,
    LOGGER,
)
from .coordinator import OverseerrConfigEntry

SERVICE_GET_REQUESTS = "get_requests"
SERVICE_SEARCH_MEDIA = "search_media"
SERVICE_REQUEST_MEDIA = "request_media"

SERVICE_GET_REQUESTS_SCHEMA = vol.Schema(
    {
        vol.Required(ATTR_CONFIG_ENTRY_ID): str,
        vol.Optional(ATTR_STATUS): vol.In(
            ["approved", "pending", "available", "processing", "unavailable", "failed"]
        ),
        vol.Optional(ATTR_SORT_ORDER): vol.In(["added", "modified"]),
        vol.Optional(ATTR_REQUESTED_BY): int,
    }
)

SERVICE_SEARCH_MEDIA_SCHEMA = vol.Schema(
    {
        vol.Required(ATTR_CONFIG_ENTRY_ID): str,
        vol.Required(ATTR_QUERY): str,
    }
)

SERVICE_REQUEST_MEDIA_SCHEMA = vol.Schema(
    {
        vol.Required(ATTR_CONFIG_ENTRY_ID): str,
        vol.Required(ATTR_MEDIA_TYPE): vol.In(["movie", "tv"]),
        vol.Required(ATTR_MEDIA_ID): vol.All(
            vol.Coerce(int),
            vol.Range(min=1),
        ),
        vol.Optional(ATTR_SEASONS): vol.Any(
            vol.Coerce(int),
            [vol.Coerce(int)],
            str,
        ),
    }
)


async def _get_media(
    client: OverseerrClient, media_type: str, identifier: int
) -> dict[str, Any]:
    """Get media details."""
    media = {}
    try:
        if media_type == "movie":
            media = asdict(await client.get_movie_details(identifier))
        if media_type == "tv":
            media = asdict(await client.get_tv_details(identifier))
    except OverseerrConnectionError:
        LOGGER.error("Could not find data for %s %s", media_type, identifier)
        return {}
    media["media_info"].pop("requests")
    return media


async def _async_get_requests(call: ServiceCall) -> ServiceResponse:
    """Get requests made to Seerr."""
    entry: OverseerrConfigEntry = service.async_get_config_entry(
        call.hass, DOMAIN, call.data[ATTR_CONFIG_ENTRY_ID]
    )
    client = entry.runtime_data.client
    kwargs: dict[str, Any] = {}
    if status := call.data.get(ATTR_STATUS):
        kwargs["status"] = status
    if sort_order := call.data.get(ATTR_SORT_ORDER):
        kwargs["sort"] = sort_order
    if requested_by := call.data.get(ATTR_REQUESTED_BY):
        kwargs["requested_by"] = requested_by
    try:
        requests = await client.get_requests(**kwargs)
    except OverseerrConnectionError as err:
        raise HomeAssistantError(
            translation_domain=DOMAIN,
            translation_key="connection_error",
            translation_placeholders={"error": str(err)},
        ) from err
    result: list[dict[str, Any]] = []
    for request in requests:
        req = asdict(request)
        assert request.media.tmdb_id
        req["media"] = await _get_media(
            client, request.media.media_type, request.media.tmdb_id
        )
        for user in (req["modified_by"], req["requested_by"]):
            del user["avatar_e_tag"]
            del user["avatar_version"]
            del user["permissions"]
            del user["recovery_link_expiration_date"]
            del user["settings"]
            del user["user_type"]
            del user["warnings"]
        result.append(req)

    return {"requests": cast(list[JsonValueType], result)}


async def _async_search_media(call: ServiceCall) -> ServiceResponse:
    """Search for media in Seerr."""
    entry: OverseerrConfigEntry = service.async_get_config_entry(
        call.hass, DOMAIN, call.data[ATTR_CONFIG_ENTRY_ID]
    )
    client = entry.runtime_data.client
    query = call.data[ATTR_QUERY]

    LOGGER.debug("Searching for '%s'", query)
    try:
        search_results = await client.search(query)
    except OverseerrConnectionError as err:
        raise HomeAssistantError(
            translation_domain=DOMAIN,
            translation_key="connection_error",
            translation_placeholders={"error": str(err)},
        ) from err

    return {
        "results": cast(
            list[JsonValueType], [asdict(result) for result in search_results]
        )
    }


async def _async_request_media(call: ServiceCall) -> ServiceResponse:
    """Request media in Seerr."""
    entry: OverseerrConfigEntry = service.async_get_config_entry(
        call.hass, DOMAIN, call.data[ATTR_CONFIG_ENTRY_ID]
    )
    client = entry.runtime_data.client
    media_type = call.data[ATTR_MEDIA_TYPE]
    media_id = call.data[ATTR_MEDIA_ID]
    seasons = parse_seasons_input(call.data.get(ATTR_SEASONS))

    LOGGER.debug(
        "Requesting %s with media ID %s (seasons: %s)",
        media_type,
        media_id,
        seasons or "none",
    )
    try:
        # We can always pass in the seasons, they will be ignored if the media type isn't TV
        request = await client.create_request(media_type, media_id, seasons)
    except OverseerrConnectionError as err:
        raise HomeAssistantError(
            translation_domain=DOMAIN,
            translation_key="connection_error",
            translation_placeholders={"error": str(err)},
        ) from err

    return {"request": cast(JsonValueType, asdict(request))}


def parse_seasons_input(seasons_input: Any | None) -> Literal["all"] | list[int]:
    """Parse all possible inputs to "all" or a list of integers."""
    seasons_str = str(seasons_input).strip()
    if seasons_input is None or seasons_str in ("", "all"):
        return "all"

    try:
        parsed = ast.literal_eval(seasons_str)
        if isinstance(parsed, int):
            return [parsed]
        return [int(season) for season in parsed]
    except ValueError, SyntaxError, TypeError:
        LOGGER.error("Unable to cast input to a list '%s'", seasons_input)
        return "all"


@callback
def async_setup_services(hass: HomeAssistant) -> None:
    """Set up the services for the Seerr integration."""

    hass.services.async_register(
        DOMAIN,
        SERVICE_GET_REQUESTS,
        _async_get_requests,
        schema=SERVICE_GET_REQUESTS_SCHEMA,
        supports_response=SupportsResponse.ONLY,
    )

    hass.services.async_register(
        DOMAIN,
        SERVICE_SEARCH_MEDIA,
        _async_search_media,
        schema=SERVICE_SEARCH_MEDIA_SCHEMA,
        supports_response=SupportsResponse.ONLY,
    )

    hass.services.async_register(
        DOMAIN,
        SERVICE_REQUEST_MEDIA,
        _async_request_media,
        schema=SERVICE_REQUEST_MEDIA_SCHEMA,
        supports_response=SupportsResponse.ONLY,
    )
