Open source Nest implementation
use crate::database::Database;
use crate::models::projects;
use crate::repoman::RepoMan;
use rocket::fs::NamedFile;
use rocket::{Route, State};

/// Because of the overlap with other routes in parameters, this route has
/// rank 2. This way the changelist matches first, and if it doesn't, this route
/// will respond. Note that in the push flow this order is reversed.
#[get("/<org_path>/<proj_path>/.pijul?<channel>&<id>", rank = 2)]
async fn remote_id(
    db: &State<Database>,
    repoman: &State<RepoMan>,
    org_path: String,
    proj_path: String,
    channel: String, // TODO check if this breaks non-UTF-8 channels
    id: Option<String>,
) -> Option<String> {
    let p = match projects::find(db, org_path, proj_path).await {
        Some(p) => p,
        None => return None,
    };

    match p.repository(&repoman.storage_root) {
        Ok(r) => r.channel_remote_id(channel, id),
        Err(e) => {
            println!("{}", e);
            None
        }
    }
}

#[get("/<org_path>/<proj_path>/.pijul?<changelist>&<channel>")]
async fn changelist(
    db: &State<Database>,
    repoman: &State<RepoMan>,
    org_path: String,
    proj_path: String,
    channel: String, // TODO check if this breaks non-UTF-8 channels
    changelist: u64,
) -> Option<String> {
    let p = projects::find(db, org_path, proj_path).await?;

    match p
        .repository(&repoman.storage_root)
        .unwrap()
        .changelist(channel, changelist)
    {
        Ok(out) => Some(out),
        Err(e) => {
            println!("error getting changelist: {}", e);
            None
        }
    }
}

use rocket::fs::TempFile;

// TODO pijul: When this endpoint returns a 4XX or 5XX status code, it thinks the push still
// succeeded. When a user cannot be authenticated the status code is 404 to hide the existence of
// the repository.
#[post("/<org_path>/<proj_path>/.pijul?<apply>", data = "<patch>")]
async fn apply(
    db: &State<Database>,
    repoman: &State<RepoMan>,
    current_user: crate::models::users::User,
    org_path: String,
    proj_path: String,
    apply: String,
    mut patch: TempFile<'_>,
) -> rocket::http::Status {
    let p = if let Some(p) = projects::find(db, org_path, proj_path).await {
        p
    } else {
        return rocket::http::Status::NotFound;
    };

    let repo = p.repository(&repoman.storage_root).unwrap();
    let hash =
        if let Some(h) = crate::models::pijul::changestores::Changestore::hash_from_string(apply) {
            h
        } else {
            return rocket::http::Status::InternalServerError;
        };

    if !crate::authz::can_apply_patch(db, &current_user, &repo).await {
        return rocket::http::Status::Unauthorized;
    }

    let patch_path = repo.changestore().change_file(hash);

    if repo.changestore().ensure_parent_dirs(hash).is_err() {
        println!("No write permission?");
        return rocket::http::Status::InternalServerError;
    }

    if patch.persist_to(&patch_path).await.is_err() {
        return rocket::http::Status::InternalServerError;
    }

    if !repo.valid_change(hash) {
        println!("Not valid as object");
        return rocket::http::Status::InternalServerError;
    }

    if repo.apply_change_to_channel("main", hash).is_ok() {
        return rocket::http::Status::Accepted;
    };

    rocket::http::Status::InternalServerError
}

#[derive(FromForm)]
struct Identities {
    identities: Vec<String>,
}

use rocket::serde::{json::Json, Serialize};
#[derive(Serialize, FromForm)]
struct IdentitieRes {
    id: Vec<String>,
    rev: u64,
}

// TODO figure out what I'm supposed to send here?
#[get("/<_>/<_>/.pijul?<identities>", rank = 4)]
fn identities(identities: Identities) -> Json<IdentitieRes> {
    println!("{:?}", identities.identities);
    Json(IdentitieRes { id: vec![], rev: 0 })
}

#[get("/<org_path>/<proj_path>/.pijul?<change>", rank = 3)]
async fn change(
    db: &State<Database>,
    repoman: &State<RepoMan>,
    org_path: String,
    proj_path: String,
    change: String,
) -> Option<NamedFile> {
    let p = projects::find(db, org_path, proj_path).await?;

    let hash = crate::models::pijul::changestores::Changestore::hash_from_string(change)?;
    let repo = p.repository(&repoman.storage_root).ok()?;
    let path = repo.changestore().change_file(hash);

    NamedFile::open(path).await.ok()
}

pub fn routes() -> Vec<Route> {
    routes![remote_id, changelist, apply, identities, change]
}