use crate::api::auth::Session;
use crate::error::ApiResult;
use crate::svc::chat_svc::ChatService;
use chrono::{DateTime, Utc};
use rocket::response::stream::Event;
use rocket::serde::json::Json;
use rocket::serde::{Deserialize, Serialize};
use rocket::{Shutdown, State, ___internal_EventStream as EventStream};
use sqlx::FromRow;
use tokio::select;
use tokio::sync::broadcast;
use crate::model::event::{ChatEvent, ChatMsg};

#[post("/chat/<channel_id>", format = "json", data = "<msg>")]
pub async fn post_message(
    msg: Json<ChatMsg>,
    chat: &State<ChatService>,
    session: Session,
    channel_id: i64,
) -> ApiResult<()> {
    chat.send(channel_id, session.uid, &msg.text, Utc::now()).await
}

#[get("/events/<channel_id>")]
pub async fn event_stream(
    chat: &State<ChatService>,
    s: Session,
    mut shutdown: Shutdown,
    channel_id: i64,
) -> ApiResult<EventStream![]> {
    let messages = chat.fetch_latest_messages_desc(channel_id, 100)
        .await?; // if get message returned err, inform user.

    let mut rx = chat.subscribe(channel_id).await;
    let id = s.uid;

    Ok(EventStream! {
        for msg in messages.into_iter().rev() {
            // tracing::info!("sending: {:?}", serde_json::to_string(&ChatEvent::SendMessage(msg.clone())).unwrap());
            yield Event::json(&ChatEvent::SendMessage(msg));
        }

        loop {
            select!{
                _ = &mut shutdown => break, // exit early on shutdown
                event = rx.recv() => match event {
                    Ok(event) => {
                        // tracing::info!("yielding event: {event:?}");
                        yield Event::json(&event)
                    },
                    Err(broadcast::error::RecvError::Lagged(n)) => {
                        tracing::warn!("Receiver lagging on channel {channel_id} by {n} events",);
                        yield Event::comment("RecvError::Lagged");
                    }
                    Err(broadcast::error::RecvError::Closed) => {
                        tracing::info!("Broadcaster hung up on channel {channel_id}!");
                        break
                    },
                },
            }
        }
    })
}