"""Controller to send and respond to heartbeat messages."""

from __future__ import annotations

import logging
import time

from . import BaseController

from ..const import MESSAGE_TYPE, PLATFORM_DESTINATION_ID
from ..error import ControllerNotRegistered, NotConnected, PyChromecastStopped

# pylint: disable-next=no-name-in-module
from ..generated.cast_channel_pb2 import CastMessage

NS_HEARTBEAT = "urn:x-cast:com.google.cast.tp.heartbeat"

TYPE_PING = "PING"
TYPE_PONG = "PONG"

HB_PING_TIME = 10
HB_PONG_TIME = 10


class HeartbeatController(BaseController):
    """Controller to send and respond to heartbeat messages."""

    def __init__(self) -> None:
        super().__init__(NS_HEARTBEAT, target_platform=True)
        self.last_ping = 0.0
        self.last_pong = time.time()
        self.logger = logging.getLogger(__name__)

    def receive_message(self, _message: CastMessage, data: dict) -> bool:
        """
        Called when a heartbeat message is received.

        data is message.payload_utf8 interpreted as a JSON dict.
        """
        if self._socket_client is None:
            raise ControllerNotRegistered

        if self._socket_client.is_stopped:
            return True

        if data[MESSAGE_TYPE] == TYPE_PING:
            try:
                self._socket_client.send_message(
                    PLATFORM_DESTINATION_ID,
                    self.namespace,
                    {MESSAGE_TYPE: TYPE_PONG},
                    no_add_request_id=True,
                )
            except PyChromecastStopped:
                self._socket_client.logger.debug(
                    "Heartbeat error when sending response, "
                    "Chromecast connection has stopped"
                )

            return True

        if data[MESSAGE_TYPE] == TYPE_PONG:
            self.reset()
            return True

        return False

    def ping(self) -> None:
        """Send a ping message."""
        if self._socket_client is None:
            raise ControllerNotRegistered

        self.last_ping = time.time()
        try:
            self.send_message({MESSAGE_TYPE: TYPE_PING})
        except NotConnected:
            self._socket_client.logger.error(
                "Chromecast is disconnected. Cannot ping until reconnected."
            )

    def reset(self) -> None:
        """Reset expired counter."""
        self.last_pong = time.time()

    def is_expired(self) -> bool:
        """Indicates if connection has expired."""
        if time.time() - self.last_ping > HB_PING_TIME:
            self.ping()

        return (time.time() - self.last_pong) > HB_PING_TIME + HB_PONG_TIME
