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 ReactionRequest {
    pub message: Uuid,
    pub emoji: String,
}

#[derive(Debug, Deserialize, utoipa::ToSchema)]
pub struct ThreadCreateRequest {
    pub parent: i64,
}

#[derive(Debug, Deserialize, utoipa::ToSchema)]
pub struct TypingRequest {
    pub action: TypingAction,
}

#[derive(Debug, Deserialize, utoipa::ToSchema)]
pub enum TypingAction {
    Start,
    Stop,
}

#[utoipa::path(
    post,
    path = "/api/v1/ws/rooms/{room_id}/reactions",
    request_body = ReactionRequest,
    responses((status = 204, description = "Reaction added")),
    tag = "channel",
)]
pub async fn reaction_add(
    req: HttpRequest,
    room_id: web::Path<Uuid>,
    body: web::Json<ReactionRequest>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::ReactionAdd {
        room: room_id.into_inner(),
        message: body.message,
        emoji: body.emoji.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/rooms/{room_id}/reactions",
    request_body = ReactionRequest,
    responses((status = 204, description = "Reaction removed")),
    tag = "channel",
)]
pub async fn reaction_remove(
    req: HttpRequest,
    room_id: web::Path<Uuid>,
    body: web::Json<ReactionRequest>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::ReactionRemove {
        room: room_id.into_inner(),
        message: body.message,
        emoji: body.emoji.clone(),
    };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}

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

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

#[utoipa::path(
    patch,
    path = "/api/v1/ws/threads/{thread_id}/resolve",
    responses((status = 200, description = "Thread resolved")),
    tag = "channel",
)]
pub async fn thread_resolve(
    req: HttpRequest,
    thread_id: web::Path<Uuid>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::ThreadResolve {
        thread_id: thread_id.into_inner(),
    };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}

#[utoipa::path(
    patch,
    path = "/api/v1/ws/threads/{thread_id}/archive",
    responses((status = 200, description = "Thread archived")),
    tag = "channel",
)]
pub async fn thread_archive(
    req: HttpRequest,
    thread_id: web::Path<Uuid>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::ThreadArchive {
        thread_id: thread_id.into_inner(),
    };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}

#[derive(Debug, Deserialize, utoipa::ToSchema)]
pub struct PinRequest {
    pub message: Uuid,
}

#[utoipa::path(
    post,
    path = "/api/v1/ws/rooms/{room_id}/pins",
    request_body = PinRequest,
    responses((status = 204, description = "Message pinned")),
    tag = "channel",
)]
pub async fn pin_add(
    req: HttpRequest,
    room_id: web::Path<Uuid>,
    body: web::Json<PinRequest>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::PinAdd {
        room: room_id.into_inner(),
        message: body.message,
    };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}

#[utoipa::path(
    delete,
    path = "/api/v1/ws/rooms/{room_id}/pins",
    request_body = PinRequest,
    responses((status = 204, description = "Pin removed")),
    tag = "channel",
)]
pub async fn pin_remove(
    req: HttpRequest,
    room_id: web::Path<Uuid>,
    body: web::Json<PinRequest>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::PinRemove {
        room: room_id.into_inner(),
        message: body.message,
    };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}

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

#[utoipa::path(
    put,
    path = "/api/v1/ws/rooms/{room_id}/drafts",
    request_body = DraftSaveRequest,
    responses((status = 204, description = "Draft saved")),
    tag = "channel",
)]
pub async fn draft_save(
    req: HttpRequest,
    room_id: web::Path<Uuid>,
    body: web::Json<DraftSaveRequest>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::DraftSave {
        room: room_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/rooms/{room_id}/drafts",
    responses((status = 204, description = "Draft cleared")),
    tag = "channel",
)]
pub async fn draft_clear(
    req: HttpRequest,
    room_id: web::Path<Uuid>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::DraftClear {
        room: room_id.into_inner(),
    };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}

#[utoipa::path(
    post,
    path = "/api/v1/ws/rooms/{room_id}/typing",
    request_body = TypingRequest,
    responses((status = 204, description = "Typing indicator broadcasted")),
    tag = "channel",
)]
pub async fn typing(
    req: HttpRequest,
    room_id: web::Path<Uuid>,
    body: web::Json<TypingRequest>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let room = room_id.into_inner();
    let msg = match body.action {
        TypingAction::Start => WsInMessage::TypingStart { room },
        TypingAction::Stop => WsInMessage::TypingStop { room },
    };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}
