mod manager;
mod package;

pub use manager::RepoGroupManager;

use axum::extract::{BodyStream, Path, State};
use axum::http::StatusCode;
use axum::response::IntoResponse;
use axum::routing::{get_service, post};
use axum::Router;
use futures::StreamExt;
use futures::TryFutureExt;
use futures::TryStreamExt;
use std::io::Read;
use std::sync::Arc;
use tokio::{fs, io, io::AsyncWriteExt};
use tower_http::services::ServeDir;
use uuid::Uuid;

pub fn router(global: &crate::Global) -> Router<crate::Global> {
    // Try to serve packages by default, and try the database files instead if not found
    let serve_repos = get_service(
        ServeDir::new(&global.config.pkg_dir).fallback(ServeDir::new(&global.config.repo_dir)),
    );
    Router::new()
        .route(
            "/:repo",
            post(post_package_archive).get(serve_repos.clone()),
        )
        .fallback(serve_repos)
        .with_state(global.clone())
}

async fn post_package_archive(
    State(global): State<crate::Global>,
    Path(repo): Path<String>,
    body: BodyStream,
) -> Result<(), StatusCode> {
    // let mut body_reader = tokio_util::io::StreamReader::new(
    //     body.map_err(|err| io::Error::new(io::ErrorKind::Other, err)),
    // );
    let mut body = body.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR);

    // We first stream the uploaded file to disk
    let uuid: uuid::fmt::Simple = Uuid::new_v4().into();
    let path = global.config.pkg_dir.join(uuid.to_string());
    let mut f = fs::File::create(&path)
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
        .await?;

    while let Some(chunk) = body.next().await {
        f.write_all(&chunk?)
            .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
            .await?;
    }

    let clone = Arc::clone(&global.repo_manager);
    tokio::task::spawn_blocking(move || clone.write().unwrap().add_pkg_from_path(&repo, &path))
        .map_err(|err| {
            println!("{}", err);
            StatusCode::INTERNAL_SERVER_ERROR
        })
        .await?
        .map_err(|err| {
            println!("{}", err);
            StatusCode::INTERNAL_SERVER_ERROR
        })
}