"""
Controller to interface with the YouTube-app.
Use the media controller to play, pause etc.
"""

import logging
import threading
from typing import Any, cast

from casttube import YouTubeSession  # type: ignore[import-untyped]
from casttube.YouTubeSession import HEADERS  # type: ignore[import-untyped]
import requests

from . import QuickPlayController
from ..const import MESSAGE_TYPE
from ..error import RequestTimeout
from ..generated.cast_channel_pb2 import (  # pylint: disable=no-name-in-module
    CastMessage,
)
from ..config import APP_YOUTUBE

YOUTUBE_NAMESPACE = "urn:x-cast:com.google.youtube.mdx"
TYPE_GET_SCREEN_ID = "getMdxSessionStatus"
TYPE_STATUS = "mdxSessionStatus"
ATTR_SCREEN_ID = "screenId"
_LOGGER = logging.getLogger(__name__)


class TimeoutYouTubeSession(YouTubeSession):  # type: ignore[misc]
    """A youtube session with timeout."""

    def __init__(self, screen_id: str, timeout: float) -> None:
        """Initialize."""
        super().__init__(screen_id)
        self.__timeout = timeout

    def _do_post(
        self,
        url: Any,
        data: Any = None,
        params: Any = None,
        headers: Any = None,
        session_request: Any = False,
    ) -> Any:
        """
        Calls requests.post with custom headers,
         increments RID(request id) on every post.
        will raise if response is not 200
        :param url:(str) request url
        :param data: (dict) the POST body
        :param params:(dict) POST url params
        :param headers:(dict) Additional headers for the request
        :param session_request:(bool) True to increment session
         request counter(req_count)
        :return: POST response
        """
        if headers:
            headers = {**HEADERS, **headers}
        else:
            headers = HEADERS
        response = requests.post(
            url, headers=headers, data=data, params=params, timeout=self.__timeout
        )
        # 404 resets the sid, session counters
        # 400 in session probably means bad sid
        # If user did a bad request (eg. remove an non-existing video from queue)
        # bind restores the session.
        if response.status_code in (404, 400) and session_request:
            self._bind()
        response.raise_for_status()
        if session_request:
            self._req_count += 1
        self._rid += 1
        return response


class YouTubeController(QuickPlayController):
    """Controller to interact with Youtube."""

    _session: YouTubeSession
    _screen_id: str | None = None

    def __init__(self, timeout: float = 10) -> None:
        super().__init__(YOUTUBE_NAMESPACE, APP_YOUTUBE)
        self.status_update_event = threading.Event()
        self._timeout = timeout
        self._session = None

    def start_session_if_none(self) -> None:
        """
        Starts a session it is not yet initialized.
        """
        if not (self._screen_id and self._session):
            self.update_screen_id()
            self._session = TimeoutYouTubeSession(
                screen_id=cast(str, self._screen_id), timeout=self._timeout
            )

    def play_video(self, video_id: str, playlist_id: str | None = None) -> None:
        """
        Play video(video_id) now. This ignores the current play queue order.
        :param video_id: YouTube video id(http://youtube.com/watch?v=video_id)
        :param playlist_id: youtube.com/watch?v=video_id&list=playlist_id
        """
        self.start_session_if_none()
        self._session.play_video(video_id, playlist_id)

    def add_to_queue(self, video_id: str) -> None:
        """
        Add video(video_id) to the end of the play queue.
        :param video_id: YouTube video id(http://youtube.com/watch?v=video_id)
        """
        self.start_session_if_none()
        self._session.add_to_queue(video_id)

    def play_next(self, video_id: str) -> None:
        """
        Play video(video_id) after the currently playing video.
        :param video_id: YouTube video id(http://youtube.com/watch?v=video_id)
        """
        self.start_session_if_none()
        self._session.play_next(video_id)

    def remove_video(self, video_id: str) -> None:
        """
        Remove video(videoId) from the queue.
        :param video_id: YouTube video id(http://youtube.com/watch?v=video_id)
        """
        self.start_session_if_none()
        self._session.remove_video(video_id)

    def clear_playlist(self) -> None:
        """
        Clear the entire video queue
        """
        self.start_session_if_none()
        self._session.clear_playlist()

    def update_screen_id(self) -> None:
        """
        Sends a getMdxSessionStatus to get the screenId and waits for response.
        This function is blocking
        If connected we should always get a response
        (send message will launch app if it is not running).
        """
        self.status_update_event.clear()
        self.send_message({MESSAGE_TYPE: TYPE_GET_SCREEN_ID})
        status = self.status_update_event.wait(10)
        if not status:
            _LOGGER.warning("Failed to update screen_id")
        self.status_update_event.clear()

    def receive_message(self, _message: CastMessage, data: dict) -> bool:
        """Called when a message is received."""
        if data[MESSAGE_TYPE] == TYPE_STATUS:
            # Ignore the type error until validation of messages has been implemented
            self._process_status(data.get("data"))  # type: ignore[arg-type]
            return True

        return False

    def _process_status(self, status: dict) -> None:
        """Process latest status update."""
        self._screen_id = status.get(ATTR_SCREEN_ID)
        self.status_update_event.set()

    def quick_play(
        self,
        *,
        media_id: str,
        timeout: float,
        playlist_id: str | None = None,
        enqueue: bool = False,
        **kwargs: Any,
    ) -> None:
        """Quick Play"""
        self._timeout = timeout

        try:
            if enqueue:
                self.add_to_queue(media_id, **kwargs)
            else:
                self.play_video(media_id, playlist_id=playlist_id, **kwargs)
        except requests.Timeout as exc:
            raise RequestTimeout(f"youtube quick play {media_id}", timeout) from exc
