// 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 std::error::Error;
use sqlx::PgPool;
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
#[repr(i32)]
pub enum Protocol {
    Any = 0,
    TCP = 1,
    UDP = 2,
    ICMP = 3
}

#[derive(Serialize, Deserialize)]
pub enum AllowFrom {
    Anyone,
    SpecificRole(i32)
}

#[derive(Serialize, Deserialize)]
pub struct FirewallRule {
    pub id: i32,
    pub protocol: Protocol,
    pub port_start: u16,
    pub port_end: u16,
    pub allow_from: AllowFrom,
    pub description: String
}

#[derive(Serialize, Deserialize)]
pub struct Role {
    pub id: i32,
    pub org_id: i32,
    pub name: String,
    pub description: String,
    pub firewall_rules: Vec<FirewallRule>
}

pub async fn get_role(role_id: i32, db: &PgPool) -> Result<Option<Role>, Box<dyn Error>> {
    let query_result: Option<_> = sqlx::query!("SELECT * FROM roles WHERE id = $1", role_id).fetch_optional(db).await?;

    if let Some(res) = query_result {
        // get all firewall rules
        let rules_res = sqlx::query!("SELECT * FROM roles_firewall_rules WHERE role = $1", Some(role_id)).fetch_all(db).await?;

        let mut rules = vec![];

        for rule in rules_res {
            rules.push(FirewallRule {
                id: rule.id,
                protocol: match rule.protocol {
                    0 => Protocol::Any,
                    1 => Protocol::TCP,
                    2 => Protocol::UDP,
                    3 => Protocol::ICMP,
                    _ => return Err(format!("invalid protocol on a firewall rule {}", rule.id).into())
                },
                port_start: rule.port_range_start as u16,
                port_end: rule.port_range_end as u16,
                allow_from: match rule.allow_from {
                    -1 => AllowFrom::Anyone,
                    _ => AllowFrom::SpecificRole(rule.allow_from)
                },
                description: rule.description,
            })
        }

        Ok(Some(Role {
            id: role_id,
            org_id: res.org,
            name: res.name,
            description: res.description,
            firewall_rules: rules,
        }))
    } else {
        Ok(None)
    }
}

pub async fn get_role_ids_for_ca(org_id: i32, db: &PgPool) -> Result<Vec<i32>, Box<dyn Error>> {
    Ok(sqlx::query!("SELECT id FROM roles WHERE org = $1", org_id).fetch_all(db).await?.iter().map(|r| r.id).collect())
}