//! Transactional outbox for chat events and the publisher abstraction consumed by the WSS hub.

use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::{Postgres, Transaction};
use uuid::Uuid;

use crate::events::ChatEventEnvelope;
use crate::ids::RoomId;

/// Row shape for `athena.chat_outbox` pending or published events.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatOutboxRow {
    pub id: Uuid,
    pub aggregate_type: String,
    pub aggregate_id: String,
    pub room_id: RoomId,
    pub room_seq: Option<i64>,
    pub event_type: String,
    pub payload_json: ChatEventEnvelope,
    pub created_at: DateTime<Utc>,
    pub published_at: Option<DateTime<Utc>>,
    pub publish_attempts: i32,
}

/// Publishes committed chat events to realtime transports (typically [`athena_wss::WsHub`]).
#[async_trait]
pub trait ChatEventPublisher: Send + Sync {
    async fn publish_room_event(
        &self,
        client_name: &str,
        room_id: RoomId,
        event: ChatEventEnvelope,
    );
}

/// No-op publisher for tests and deployments without a WebSocket gateway.
#[derive(Debug, Default)]
pub struct NoopChatEventPublisher;

#[async_trait]
impl ChatEventPublisher for NoopChatEventPublisher {
    async fn publish_room_event(
        &self,
        _client_name: &str,
        _room_id: RoomId,
        _event: ChatEventEnvelope,
    ) {
    }
}

/// Inserts an outbox row inside an open SQL transaction.
///
/// # Errors
///
/// Returns [`sqlx::Error`] when the insert fails or the returned `id` cannot be read.
pub async fn insert_chat_outbox_tx(
    tx: &mut Transaction<'_, Postgres>,
    aggregate_type: &str,
    aggregate_id: &str,
    room_id: RoomId,
    room_seq: Option<i64>,
    event_type: &str,
    payload: &ChatEventEnvelope,
) -> Result<Uuid, sqlx::Error> {
    let row = sqlx::query(
        r#"
        INSERT INTO athena.chat_outbox
            (aggregate_type, aggregate_id, room_id, room_seq, event_type, payload_json)
        VALUES
            ($1, $2, $3, $4, $5, $6)
        RETURNING id
        "#,
    )
    .bind(aggregate_type)
    .bind(aggregate_id)
    .bind(room_id)
    .bind(room_seq)
    .bind(event_type)
    .bind(sqlx::types::Json(payload))
    .fetch_one(&mut **tx)
    .await?;

    Ok(sqlx::Row::try_get(&row, "id")?)
}
