// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
//     Copyright (C) 2023 c0repwn3r
//
//     This program is free software: you can redistribute it and/or modify
//     it under the terms of the GNU General Public License as published by
//     the Free Software Foundation, either version 3 of the License, or
//     (at your option) any later version.
//
//     This program is distributed in the hope that it will be useful,
//     but WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//     GNU General Public License for more details.
//
//     You should have received a copy of the GNU General Public License
//     along with this program.  If not, see <https://www.gnu.org/licenses/>.

use log::error;
use rocket::{options, get, State, post};
use rocket::http::{ContentType, Status};
use rocket::serde::json::Json;

use serde::{Serialize, Deserialize};
use sqlx::PgPool;
use crate::auth::TOTPAuthenticatedUserInfo;
use crate::org::user_has_org_assoc;
use crate::role::{get_role, get_role_ids_for_ca, Role};

#[options("/v1/org/<_org>/roles")]
pub fn options(_org: i32) -> &'static str { "" }
#[options("/v1/org/<_org>/role")]
pub fn options_roleadd(_org: i32) -> &'static str { "" }

#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct RolesResponse {
    pub data: Vec<Role>
}

#[get("/v1/org/<org>/roles")]
pub async fn get_roles(org: i32, user: TOTPAuthenticatedUserInfo, db: &State<PgPool>) -> Result<(ContentType, Json<RolesResponse>), (Status, String)> {
    if !user_has_org_assoc(user.user_id, org, db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))? {
        return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_MISSING_ORG_AUTHORIZATION", "this user does not have permission to access this org")));
    }

    let roles = match get_role_ids_for_ca(org, db.inner()).await {
        Ok(r) => r,
        Err(e) => {
            return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_INTERNAL_SERVER_ERR", "there was an error querying the db, please try again later", e)))
        }
    };

    let mut resp = RolesResponse {
        data: vec![]
    };

    for role in roles {
        let role_info = match get_role(role, db.inner()).await {
            Ok(r) => r,
            Err(e) => {
                return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_INTERNAL_SERVER_ERR", "there was an error querying the db, please try again later", e)))
            }
        };
        if let Some(info) = role_info {
            resp.data.push(info);
        } else {
            return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_INTERNAL_SERVER_ERR", "there was an error querying the db, please try again later", "missing role as returned by server - possibly deleted inbetween?")))
        }
    }

    Ok((ContentType::JSON, Json(resp)))
}

#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct RoleaddReq {
    pub name: String,
    pub description: String
}

#[post("/v1/org/<org>/role", data = "<req>")]
pub async fn role_add(req: Json<RoleaddReq>, org: i32, user: TOTPAuthenticatedUserInfo, db: &State<PgPool>) -> Result<(ContentType, String), (Status, String)> {
    if !user_has_org_assoc(user.user_id, org, db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))? {
        return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_MISSING_ORG_AUTHORIZATION", "this user does not have permission to access this org")));
    }

    match sqlx::query!("INSERT INTO roles (org, name, description) VALUES ($1, $2, $3)", org, req.name, req.description).execute(db.inner()).await {
        Ok(_) => (),
        Err(e) => {
            error!("[tfapi] dberror: {}", e);
            return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_INTERNAL_SERVER_ERR", "database returned error while trying to create role", "unable to insert role")))
        }
    }

    Ok((ContentType::JSON, "{}".to_string()))
}