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 ArticleCreateRequest {
    pub title: String,
    pub cover_url: Option<String>,
    pub content: String,
    pub content_type: Option<String>,
    pub summary: Option<String>,
    pub tags: Option<Vec<String>>,
    pub status: Option<String>,
}

#[derive(Debug, Deserialize, utoipa::ToSchema)]
pub struct ArticleUpdateRequest {
    pub title: Option<String>,
    pub cover_url: Option<String>,
    pub content: Option<String>,
    pub content_type: Option<String>,
    pub summary: Option<String>,
    pub tags: Option<Vec<String>>,
    pub is_pinned: Option<bool>,
    pub status: Option<String>,
}

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

#[utoipa::path(
    post,
    path = "/api/v1/ws/channels/{channel_id}/articles",
    request_body = ArticleCreateRequest,
    responses((status = 201, description = "Article created")),
    tag = "channel",
)]
pub async fn article_create(
    req: HttpRequest,
    channel_id: web::Path<Uuid>,
    body: web::Json<ArticleCreateRequest>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::ArticleCreate {
        channel: channel_id.into_inner(),
        title: body.title.clone(),
        cover_url: body.cover_url.clone(),
        content: body.content.clone(),
        content_type: body.content_type.clone(),
        summary: body.summary.clone(),
        tags: body.tags.clone(),
        status: body.status.clone(),
    };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(created_json(result))
}

#[utoipa::path(
    get,
    path = "/api/v1/ws/channels/{channel_id}/articles",
    params(ArticleListQuery),
    responses((status = 200, description = "Article list (waterfall feed)")),
    tag = "channel",
)]
pub async fn article_list(
    req: HttpRequest,
    channel_id: web::Path<Uuid>,
    query: web::Query<ArticleListQuery>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::ArticleList {
        channel: channel_id.into_inner(),
        before: query.before,
        limit: query.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/channels/{channel_id}/articles/{article_id}",
    responses((status = 200, description = "Article detail")),
    tag = "channel",
)]
pub async fn article_get(
    req: HttpRequest,
    path: web::Path<(Uuid, Uuid)>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let (_channel_id, article_id) = path.into_inner();
    let msg = WsInMessage::ArticleGet { article_id };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}

#[utoipa::path(
    patch,
    path = "/api/v1/ws/channels/{channel_id}/articles/{article_id}",
    request_body = ArticleUpdateRequest,
    responses((status = 200, description = "Article updated")),
    tag = "channel",
)]
pub async fn article_update(
    req: HttpRequest,
    path: web::Path<(Uuid, Uuid)>,
    body: web::Json<ArticleUpdateRequest>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let (_channel_id, article_id) = path.into_inner();
    let msg = WsInMessage::ArticleUpdate {
        article_id,
        title: body.title.clone(),
        cover_url: body.cover_url.clone(),
        content: body.content.clone(),
        content_type: body.content_type.clone(),
        summary: body.summary.clone(),
        tags: body.tags.clone(),
        is_pinned: body.is_pinned,
        status: body.status.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/channels/{channel_id}/articles/{article_id}",
    responses((status = 204, description = "Article deleted")),
    tag = "channel",
)]
pub async fn article_delete(
    req: HttpRequest,
    path: web::Path<(Uuid, Uuid)>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let (_channel_id, article_id) = path.into_inner();
    let msg = WsInMessage::ArticleDelete { article_id };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}

#[derive(Debug, Deserialize, utoipa::ToSchema)]
pub struct ArticleLikeRequest {
    pub like: bool,
}

#[derive(Debug, Deserialize, utoipa::ToSchema)]
pub struct ArticleCommentCreateRequest {
    pub content: String,
    pub parent: Option<Uuid>,
}

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

#[utoipa::path(
    post,
    path = "/api/v1/ws/articles/{article_id}/like",
    request_body = ArticleLikeRequest,
    responses((status = 200, description = "Like toggled")),
    tag = "channel",
)]
pub async fn article_like(
    req: HttpRequest,
    article_id: web::Path<Uuid>,
    body: web::Json<ArticleLikeRequest>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::ArticleLike {
        article_id: article_id.into_inner(),
        like: body.like,
    };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}

#[utoipa::path(
    post,
    path = "/api/v1/ws/articles/{article_id}/comments",
    request_body = ArticleCommentCreateRequest,
    responses((status = 201, description = "Comment created")),
    tag = "channel",
)]
pub async fn article_comment_create(
    req: HttpRequest,
    article_id: web::Path<Uuid>,
    body: web::Json<ArticleCommentCreateRequest>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::ArticleCommentCreate {
        article_id: article_id.into_inner(),
        content: body.content.clone(),
        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/articles/{article_id}/comments",
    params(ArticleCommentListQuery),
    responses((status = 200, description = "Comment list")),
    tag = "channel",
)]
pub async fn article_comment_list(
    req: HttpRequest,
    article_id: web::Path<Uuid>,
    query: web::Query<ArticleCommentListQuery>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::ArticleCommentList {
        article_id: article_id.into_inner(),
        before: query.before,
        limit: query.limit,
    };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}

#[utoipa::path(
    delete,
    path = "/api/v1/ws/articles/{article_id}/comments/{comment_id}",
    responses((status = 204, description = "Comment deleted")),
    tag = "channel",
)]
pub async fn article_comment_delete(
    req: HttpRequest,
    path: web::Path<(Uuid, Uuid)>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let (_article_id, comment_id) = path.into_inner();
    let msg = WsInMessage::ArticleCommentDelete { comment_id };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}

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

#[utoipa::path(
    get,
    path = "/api/v1/ws/articles/{article_id}/likes",
    params(ArticleLikedUsersQuery),
    responses((status = 200, description = "List of users who liked")),
    tag = "channel",
)]
pub async fn article_liked_users(
    req: HttpRequest,
    article_id: web::Path<Uuid>,
    query: web::Query<ArticleLikedUsersQuery>,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let msg = WsInMessage::ArticleLikedUsers {
        article_id: article_id.into_inner(),
        before: query.before,
        limit: query.limit,
    };
    let result = WsHandler::handle(&bus, user_id, msg)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}
