use std::fs;

use dsx_common::config::DsxConfig;
use rocket::{Data, data::ToByteUnit, serde::json::Json};
use serde::Deserialize;

use crate::{
    error::ApiError,
    model::{DATA_DIR, PackageHandle, PackageMeta},
};

#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct NewRepo<'r> {
    name: &'r str,
}

// Create repo
#[post("/pkg", data = "<repo>")]
pub fn create_repo(repo: Json<NewRepo<'_>>) -> Result<(), &'static str> {
    let path = DATA_DIR.join("repos").join(repo.name);

    if repo.name.is_empty() {
        return Err("Repository name cannot be empty!");
    }

    if path.exists() {
        tracing::info!(
            "Attempt to create repository '{}' which already exists.",
            repo.name
        );
        return Err("This repository already exists!");
    }

    if let Err(e) = fs::create_dir_all(path) {
        tracing::error!(
            "Attempted to create package with name {} - Error: {e},",
            repo.name
        );
        return Err("Internal server error");
    }

    Ok(())
}

// Repo status/metadata
#[get("/pkg/<id>")]
pub fn get_pkg(id: &str) -> Result<Json<(PackageMeta, DsxConfig)>, ApiError> {
    let mut handle = PackageHandle::new(id);
    let meta = handle.get_meta()?;
    let config = handle.get_config()?;
    Ok(Json((meta, config)))
}

// Upload source tarball
#[post("/pkg/<id>/push", data = "<data>")]
pub async fn push_tarball(id: &str, data: Data<'_>) -> Result<(), ApiError> {
    let mut handle = PackageHandle::new(id);
    let repo = handle.path();

    let tmp_path = repo.join("upload.tar.gz");
    let stream = data
        .open(256.mebibytes())
        .into_file(&tmp_path)
        .await
        .map_err(|e| ApiError::ServerError(e.to_string()))?;

    if !stream.is_complete() {
        return Err(ApiError::BadRequest("Incomplete upload".to_string()));
    }

    // Unpack over the existing repo dir.
    // if the repo is already deleted that's fine so ignore err
    if handle.path().exists() {
        let _ = fs::remove_dir_all(repo.join("repo"));
    }

    handle.unpack(&tmp_path)?;
    fs::remove_file(&tmp_path).ok();

    // we don't care if there's an error in the build.
    let _ = handle.build();

    Ok(())
}

// Download source tarball
#[get("/pkg/<id>/pull")]
pub fn pull_tarball(
    id: &str,
) -> Result<(rocket::http::ContentType, Vec<u8>), Json<ApiError>> {
    if let Ok(tarball) = PackageHandle::new(id).tarball() {
        Ok((
            rocket::http::ContentType::new("application", "octet-stream"),
            tarball,
        ))
    } else {
        Err(Json(ApiError::NotFound(format!(
            "repo with id {id} does not exist"
        ))))
    }
}

// Download compiled binary
#[get("/pkg/<id>/artifact")]
pub fn download_artifact(
    id: &str,
) -> Result<(rocket::http::ContentType, Vec<u8>), Json<ApiError>> {
    if let Ok(artifact) = PackageHandle::new(id).get_artifact() {
        Ok((
            rocket::http::ContentType::new("application", "octet-stream"),
            artifact,
        ))
    } else {
        Err(Json(ApiError::NotFound(format!(
            "repo with id {id} does not have a valid artifact"
        ))))
    }
}
