use actix_web::{HttpRequest, HttpResponse, web};
use channel::ChannelBus;
use channel::http::{WsHandler, WsInMessage};
use serde::Deserialize;
use uuid::Uuid;

use super::rest::{channel_err, created_json, extract_user, ok_json};
use crate::error::ApiError;

#[derive(Debug, Deserialize, utoipa::ToSchema)]
pub struct CreateMessageRequest {
    pub content: String,
    pub content_type: Option<String>,
    pub thread: Option<Uuid>,
    pub in_reply_to: Option<Uuid>,
}

#[derive(Debug, Deserialize, utoipa::ToSchema)]
pub struct UpdateMessageRequest {
    pub content: String,
}

#[derive(Debug, Deserialize, utoipa::IntoParams)]
pub struct MessageListParams {
    pub before_seq: Option<i64>,
    pub after_seq: Option<i64>,
    pub limit: Option<u64>,
}

#[derive(Debug, Deserialize, utoipa::IntoParams)]
pub struct MessageAroundParams {
    pub seq: i64,
    pub limit: Option<u64>,
    pub thread: Option<Uuid>,
}

#[derive(Debug, Deserialize, utoipa::IntoParams)]
pub struct MissedMessagesParams {
    pub after_seq: i64,
    pub limit: Option<i64>,
}

#[derive(Debug, Deserialize, utoipa::IntoParams)]
pub struct SearchParams {
    pub q: String,
    pub room: Option<Uuid>,
    pub limit: Option<u64>,
    pub offset: Option<u64>,
}

#[utoipa::path(
    post,
    path = "/api/v1/ws/rooms/{room_id}/messages",
    request_body = CreateMessageRequest,
    responses((status = 201, description = "Message created")),
    tag = "channel",
)]
pub async fn create_message(
    req: HttpRequest,
    room_id: web::Path<Uuid>,
    body: web::Json<CreateMessageRequest>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::MessageCreate {
        room: room_id.into_inner(),
        content: body.content.clone(),
        content_type: body.content_type.clone(),
        thread: body.thread,
        in_reply_to: body.in_reply_to,
    };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(created_json(result))
}

#[utoipa::path(
    patch,
    path = "/api/v1/ws/messages/{message_id}",
    request_body = UpdateMessageRequest,
    responses((status = 200, description = "Message updated")),
    tag = "channel",
)]
pub async fn update_message(
    req: HttpRequest,
    message_id: web::Path<Uuid>,
    body: web::Json<UpdateMessageRequest>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::MessageUpdate {
        message: message_id.into_inner(),
        content: body.content.clone(),
    };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}

#[utoipa::path(
    delete,
    path = "/api/v1/ws/messages/{message_id}",
    responses((status = 200, description = "Message revoked")),
    tag = "channel",
)]
pub async fn revoke_message(
    req: HttpRequest,
    message_id: web::Path<Uuid>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::MessageRevoke {
        message: message_id.into_inner(),
    };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}

#[utoipa::path(
    get,
    path = "/api/v1/ws/rooms/{room_id}/messages",
    params(MessageListParams),
    responses((status = 200, description = "Message list")),
    tag = "channel",
)]
pub async fn list_messages(
    req: HttpRequest,
    room_id: web::Path<Uuid>,
    params: web::Query<MessageListParams>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::MessageList {
        room: room_id.into_inner(),
        before_seq: params.before_seq,
        after_seq: params.after_seq,
        limit: params.limit,
    };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}

#[utoipa::path(
    get,
    path = "/api/v1/ws/rooms/{room_id}/messages/around",
    params(MessageAroundParams),
    responses((status = 200, description = "Messages around seq")),
    tag = "channel",
)]
pub async fn messages_around(
    req: HttpRequest,
    room_id: web::Path<Uuid>,
    params: web::Query<MessageAroundParams>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::MessageAround {
        room: room_id.into_inner(),
        seq: params.seq,
        limit: params.limit,
        thread: params.thread,
    };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}

#[utoipa::path(
    get,
    path = "/api/v1/ws/rooms/{room_id}/messages/missed",
    params(MissedMessagesParams),
    responses((status = 200, description = "Missed messages")),
    tag = "channel",
)]
pub async fn missed_messages(
    req: HttpRequest,
    room_id: web::Path<Uuid>,
    params: web::Query<MissedMessagesParams>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::MissedMessages {
        room: room_id.into_inner(),
        after_seq: params.after_seq,
        limit: params.limit,
    };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}

#[utoipa::path(
    get,
    path = "/api/v1/ws/search",
    params(SearchParams),
    responses((status = 200, description = "Search results")),
    tag = "channel",
)]
pub async fn search(
    req: HttpRequest,
    params: web::Query<SearchParams>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::Search {
        q: params.q.clone(),
        room: params.room,
        start_time: None,
        end_time: None,
        sender_id: None,
        content_type: None,
        limit: params.limit,
        offset: params.offset,
    };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}
