use axum::{
    Router,
    extract::{Path, Query, State},
    http::StatusCode,
    response::IntoResponse,
    routing::{delete, get, put},
};
use bytes::Bytes;
use object_store::ObjectStore;
use serde::Deserialize;
use std::sync::Arc;

use crate::ops;

pub type StorageState = Arc<dyn ObjectStore>;

pub fn router(store: Arc<dyn ObjectStore>) -> Router {
    Router::new()
        .route("/health", get(health))
        .route("/objects/{*key}", put(put_object))
        .route("/objects/{*key}", get(get_object))
        .route("/objects/{*key}", delete(delete_object))
        .route("/objects", get(list_objects))
        .with_state(store)
}

async fn health() -> &'static str {
    "storaged ok"
}

async fn put_object(
    State(store): State<StorageState>,
    Path(key): Path<String>,
    body: Bytes,
) -> impl IntoResponse {
    match ops::put(&store, &key, body).await {
        Ok(()) => (StatusCode::CREATED, format!("stored: {key}")),
        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e),
    }
}

async fn get_object(
    State(store): State<StorageState>,
    Path(key): Path<String>,
) -> impl IntoResponse {
    match ops::get(&store, &key).await {
        Ok(data) => Ok(data),
        Err(e) => Err((StatusCode::NOT_FOUND, e)),
    }
}

async fn delete_object(
    State(store): State<StorageState>,
    Path(key): Path<String>,
) -> impl IntoResponse {
    match ops::delete(&store, &key).await {
        Ok(()) => (StatusCode::OK, format!("deleted: {key}")),
        Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e),
    }
}

#[derive(Deserialize)]
struct ListQuery {
    prefix: Option<String>,
}

async fn list_objects(
    State(store): State<StorageState>,
    Query(q): Query<ListQuery>,
) -> impl IntoResponse {
    match ops::list(&store, q.prefix.as_deref()).await {
        Ok(keys) => Ok(axum::Json(keys)),
        Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, e)),
    }
}
