"""
Commands client for Daisi SDK.
"""

from typing import Iterator, Optional

import grpc

from protos.v1 import Commands_pb2, Commands_pb2_grpc
from protos.v1.models import CommandModels_pb2

from ..base_client import BaseClient
from ..config import DaisiConfig
from ..exceptions import DaisiError


class HostCommandsClient(BaseClient):
    """Client for host command operations."""

    def __init__(self, config: Optional[DaisiConfig] = None) -> None:
        """Initialize host commands client.

        Args:
            config: Daisi configuration.
        """
        super().__init__(config)
        self._stub: Optional[Commands_pb2_grpc.HostCommandsProtoStub] = None

    @property
    def stub(self) -> Commands_pb2_grpc.HostCommandsProtoStub:
        """Get or create the gRPC stub."""
        if self._stub is None:
            self._stub = Commands_pb2_grpc.HostCommandsProtoStub(self._get_channel())
        return self._stub

    def open_stream(
        self, commands: Iterator[CommandModels_pb2.Command]
    ) -> Iterator[CommandModels_pb2.Command]:
        """Open bidirectional command stream.

        Args:
            commands: Iterator of commands to send.

        Yields:
            Commands received from the host.

        Raises:
            DaisiError: If stream fails.
        """
        try:
            yield from self.stub.Open(commands, metadata=self._get_metadata())
        except grpc.RpcError as e:
            raise DaisiError(f"Host command stream failed: {e.details()}") from e


class AppCommandsClient(BaseClient):
    """Client for app command operations."""

    def __init__(self, config: Optional[DaisiConfig] = None) -> None:
        """Initialize app commands client.

        Args:
            config: Daisi configuration.
        """
        super().__init__(config)
        self._stub: Optional[Commands_pb2_grpc.AppCommandsProtoStub] = None

    @property
    def stub(self) -> Commands_pb2_grpc.AppCommandsProtoStub:
        """Get or create the gRPC stub."""
        if self._stub is None:
            self._stub = Commands_pb2_grpc.AppCommandsProtoStub(self._get_channel())
        return self._stub

    def open_stream(
        self, commands: Iterator[CommandModels_pb2.Command]
    ) -> Iterator[CommandModels_pb2.Command]:
        """Open bidirectional command stream.

        Args:
            commands: Iterator of commands to send.

        Yields:
            Commands received from the app.

        Raises:
            DaisiError: If stream fails.
        """
        try:
            yield from self.stub.Open(commands, metadata=self._get_metadata())
        except grpc.RpcError as e:
            raise DaisiError(f"App command stream failed: {e.details()}") from e
