"""NoteFlow gRPC client for Flet app integration."""

from __future__ import annotations

import queue
import threading
from typing import TYPE_CHECKING, Final

import grpc

from noteflow.grpc._client_mixins import (
    AnnotationClientMixin,
    DiarizationClientMixin,
    ExportClientMixin,
    MeetingClientMixin,
    StreamingClientMixin,
)
from noteflow.grpc._config import STREAMING_CONFIG
from noteflow.grpc._types import (
    AnnotationInfo,
    ConnectionCallback,
    DiarizationResult,
    ExportResult,
    MeetingInfo,
    RenameSpeakerResult,
    ServerInfo,
    TranscriptCallback,
    TranscriptSegment,
)
from noteflow.grpc.proto import noteflow_pb2, noteflow_pb2_grpc
from noteflow.infrastructure.logging import get_logger

if TYPE_CHECKING:
    import numpy as np
    from numpy.typing import NDArray

    from noteflow.grpc._client_mixins.protocols import NoteFlowServiceStubProtocol

# Re-export types for backward compatibility
__all__ = [
    "AnnotationInfo",
    "ConnectionCallback",
    "DiarizationResult",
    "ExportResult",
    "MeetingInfo",
    "NoteFlowClient",
    "RenameSpeakerResult",
    "ServerInfo",
    "TranscriptCallback",
    "TranscriptSegment",
]

logger = get_logger(__name__)

DEFAULT_SERVER: Final[str] = "localhost:50051"
CHUNK_TIMEOUT: Final[float] = 0.1  # Timeout for getting chunks from queue


class NoteFlowClient(
    MeetingClientMixin,
    StreamingClientMixin,
    AnnotationClientMixin,
    ExportClientMixin,
    DiarizationClientMixin,
):
    """gRPC client for NoteFlow server.

    Provides async-safe methods for Flet app integration.
    """

    def __init__(
        self,
        server_address: str = DEFAULT_SERVER,
        on_transcript: TranscriptCallback | None = None,
        on_connection_change: ConnectionCallback | None = None,
    ) -> None:
        """Initialize the client.

        Args:
            server_address: Server address (host:port).
            on_transcript: Callback for transcript updates.
            on_connection_change: Callback for connection state changes.
        """
        self._server_address = server_address
        self.on_transcript = on_transcript
        self.on_connection_change = on_connection_change

        self._channel: grpc.Channel | None = None
        self._stub: NoteFlowServiceStubProtocol | None = None
        self._connected = False

        # Streaming state
        self.stream_thread: threading.Thread | None = None
        self.audio_queue: queue.Queue[tuple[str, NDArray[np.float32], float]] = queue.Queue(
            maxsize=STREAMING_CONFIG.QUEUE_MAX_SIZE
        )
        self.stop_streaming_event = threading.Event()
        self.current_meeting_id: str | None = None

    @property
    def stub(self) -> NoteFlowServiceStubProtocol | None:
        """Get the gRPC service stub."""
        return self._stub

    @property
    def connected(self) -> bool:
        """Check if connected to server."""
        return self._connected

    @property
    def server_address(self) -> str:
        """Get server address."""
        return self._server_address

    def connect(self, timeout: float = 5.0) -> bool:
        """Connect to the server.

        Args:
            timeout: Connection timeout in seconds.

        Returns:
            True if connected successfully.
        """
        try:
            return self._setup_grpc_channel(timeout)
        except grpc.FutureTimeoutError:
            logger.error("Connection timeout: %s", self._server_address)
            self.notify_connection(False, "Connection timeout")
            return False
        except grpc.RpcError as e:
            logger.error("Connection failed: %s", e)
            self.notify_connection(False, str(e))
            return False

    def _setup_grpc_channel(self, timeout: float) -> bool:
        """Set up the gRPC channel and stub.

        Args:
            timeout: Connection timeout in seconds.

        Returns:
            True if connection succeeded.
        """
        self._channel = grpc.insecure_channel(
            self._server_address,
            options=[
                ("grpc.max_send_message_length", 100 * 1024 * 1024),
                ("grpc.max_receive_message_length", 100 * 1024 * 1024),
            ],
        )

        # Wait for channel to be ready
        grpc.channel_ready_future(self._channel).result(timeout=timeout)

        self._stub = noteflow_pb2_grpc.NoteFlowServiceStub(self._channel)
        self._connected = True

        logger.info("Connected to server at %s", self._server_address)
        self.notify_connection(True, "Connected")

        return True

    def require_connection(self) -> NoteFlowServiceStubProtocol:
        """Ensure the client is connected and return the stub."""
        if self._stub is None:
            raise ConnectionError("Not connected")
        return self._stub

    def disconnect(self) -> None:
        """Disconnect from the server."""
        self.stop_streaming()

        if self._channel is not None:
            self._channel.close()
            self._channel = None
            self._stub = None

        self._connected = False
        logger.info("Disconnected from server")
        self.notify_connection(False, "Disconnected")

    def get_server_info(self) -> ServerInfo | None:
        """Get server information.

        Returns:
            ServerInfo or None if request fails.
        """
        if not self._stub:
            return None

        try:
            response = self._stub.GetServerInfo(noteflow_pb2.ServerInfoRequest())
            return ServerInfo(
                version=response.version,
                asr_model=response.asr_model,
                asr_ready=response.asr_ready,
                uptime_seconds=response.uptime_seconds,
                active_meetings=response.active_meetings,
                diarization_enabled=response.diarization_enabled,
                diarization_ready=response.diarization_ready,
            )
        except grpc.RpcError as e:
            logger.error("Failed to get server info: %s", e)
            return None
