use actix_web::{HttpRequest, HttpResponse, web};
use channel::http::{WsHandler, WsInMessage, WsOutEvent};
use channel::{ChannelBus, ChannelError};
use session::SessionExt;
use uuid::Uuid;

use crate::error::ApiError;

pub fn extract_user(req: &HttpRequest) -> Result<Uuid, ApiError> {
    req.get_session()
        .user()
        .ok_or_else(|| ApiError(service::error::AppError::Unauthorized))
}

pub fn channel_err(e: ChannelError) -> ApiError {
    ApiError(match e {
        ChannelError::Unauthorized | ChannelError::TokenInvalidOrExpired => {
            service::error::AppError::Unauthorized
        }
        ChannelError::AccessDenied => {
            service::error::AppError::PermissionDenied
        }
        ChannelError::Validation(msg) => {
            service::error::AppError::BadRequest(msg)
        }
        ChannelError::RateLimitExceeded => {
            service::error::AppError::BadRequest("rate limit exceeded".into())
        }
        ChannelError::RenewalLimitExceeded => {
            service::error::AppError::BadRequest(
                "renewal limit exceeded".into(),
            )
        }
        ChannelError::RoomNotFound => {
            service::error::AppError::NotFound("room not found".into())
        }
        ChannelError::UserNotFound => {
            service::error::AppError::NotFound("user not found".into())
        }
        ChannelError::Internal(msg) => {
            service::error::AppError::InternalServerError(msg)
        }
        ChannelError::Database(e) => {
            service::error::AppError::InternalServerError(e.to_string())
        }
        ChannelError::Cache(e) => {
            service::error::AppError::InternalServerError(e.to_string())
        }
        ChannelError::SocketIo(e) => {
            service::error::AppError::InternalServerError(e.to_string())
        }
        ChannelError::Serialization(e) => {
            service::error::AppError::InternalServerError(e.to_string())
        }
        ChannelError::Redis(e) => {
            service::error::AppError::InternalServerError(e.to_string())
        }
        ChannelError::Storage(e) => {
            service::error::AppError::InternalServerError(e.to_string())
        }
    })
}

pub fn ok_json(event: Option<WsOutEvent>) -> HttpResponse {
    match event {
        Some(e) => HttpResponse::Ok().json(e),
        None => HttpResponse::NoContent().finish(),
    }
}

pub fn created_json(event: Option<WsOutEvent>) -> HttpResponse {
    match event {
        Some(e) => HttpResponse::Created().json(e),
        None => HttpResponse::NoContent().finish(),
    }
}

#[utoipa::path(
    get,
    path = "/api/v1/ws/ping",
    responses((status = 200, description = "Pong with protocol version")),
    tag = "channel",
)]
pub async fn ping(
    req: HttpRequest,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let result = WsHandler::handle(&bus, user_id, WsInMessage::Ping)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}

#[utoipa::path(
    get,
    path = "/api/v1/ws/csrf",
    responses((status = 200, description = "CSRF token")),
    tag = "channel",
)]
pub async fn csrf_token(
    req: HttpRequest,
    bus: web::Data<ChannelBus>,
) -> Result<HttpResponse, ApiError> {
    let user_id = extract_user(&req)?;
    let result = WsHandler::handle(&bus, user_id, WsInMessage::CsrfToken)
        .await
        .map_err(channel_err)?;
    Ok(ok_json(result))
}
