//! Offline outbox — queue messages for deferred delivery.

use bytes::Bytes;
use prost::Message;
use tracing::{debug, warn};

use quicproquo_proto::method_ids;
use quicproquo_proto::qpq::v1::{EnqueueRequest, EnqueueResponse};
use quicproquo_rpc::client::RpcClient;

use crate::conversation::{ConversationId, ConversationStore};
use crate::error::SdkError;

/// Queue a message for sending when connectivity is restored.
pub fn queue_outbox(
    conv_store: &ConversationStore,
    conv_id: &ConversationId,
    recipient_key: &[u8],
    payload: &[u8],
) -> Result<(), SdkError> {
    conv_store
        .enqueue_outbox(conv_id, recipient_key, payload)
        .map_err(|e| SdkError::Storage(format!("enqueue outbox: {e}")))
}

/// Process all pending outbox entries — send them to the server.
///
/// Returns the number of entries successfully sent.
pub async fn flush_outbox(
    rpc: &RpcClient,
    conv_store: &ConversationStore,
) -> Result<usize, SdkError> {
    let entries = conv_store
        .load_pending_outbox()
        .map_err(|e| SdkError::Storage(format!("load outbox: {e}")))?;

    let mut sent = 0usize;
    for entry in &entries {
        let req = EnqueueRequest {
            recipient_key: entry.recipient_key.clone(),
            payload: entry.payload.clone(),
            channel_id: Vec::new(),
            ttl_secs: 0,
        };
        match rpc
            .call(method_ids::ENQUEUE, Bytes::from(req.encode_to_vec()))
            .await
        {
            Ok(resp_bytes) => {
                if let Err(e) = EnqueueResponse::decode(resp_bytes) {
                    warn!(outbox_id = entry.id, "decode enqueue response: {e}");
                }
                conv_store
                    .mark_outbox_sent(entry.id)
                    .map_err(|e| SdkError::Storage(format!("mark_outbox_sent: {e}")))?;
                sent += 1;
                debug!(outbox_id = entry.id, "outbox entry sent");
            }
            Err(e) => {
                warn!(outbox_id = entry.id, "outbox send failed: {e}");
                conv_store
                    .mark_outbox_failed(entry.id, entry.retry_count + 1)
                    .map_err(|e| SdkError::Storage(format!("mark_outbox_failed: {e}")))?;
            }
        }
    }

    Ok(sent)
}

/// Get the number of pending outbox entries.
pub fn outbox_count(conv_store: &ConversationStore) -> Result<usize, SdkError> {
    conv_store
        .count_pending_outbox()
        .map_err(|e| SdkError::Storage(format!("count outbox: {e}")))
}
