use anyhow::{anyhow, Result};
use fastly::http::{header, StatusCode};
use fastly::{Error, Request, Response};
use std::collections::HashSet;

/// The name of a backend server associated with this service.
const BACKEND_NAME: &str = "origin_0";

#[fastly::main]
fn main(req: Request) -> Result<Response, Error> {
    // Log initial setup
    log_fastly::init_simple("my_log", log::LevelFilter::Info);

    match do_req_with_permission_check(req) {
        Ok(resp) => Ok(resp),
        Err(e) => {
            log::info!("{}", e);
            Ok(Response::from_status(StatusCode::FORBIDDEN))
        }
    }
}

/// Request to backend if intersection of permission is found
fn do_req_with_permission_check(req: Request) -> Result<Response> {
    // Get permission from requestion
    let req_permission = get_req_permission(&req)?;

    // Send request and get response from backend
    let resp = req.send(BACKEND_NAME)?;

    // Get permission from require-group response header
    let require_group = resp
        .get_header("require-group")
        .ok_or_else(|| anyhow!("No require-group header found in response"))?
        .to_str()?;

    // Check if there is any intersection between two permission sets
    let a_set = permissions_into_set(&req_permission);
    let b_set = permissions_into_set(require_group);
    let mut intersection_set = a_set.intersection(&b_set);
    if intersection_set.next().is_none() {
        return Err(anyhow!("No intersection in permission found"));
    }

    Ok(resp)
}

/// Get permission string from request cookie
fn get_req_permission(req: &Request) -> Result<String> {
    let cookie_val: &str = req
        .get_header(header::COOKIE)
        .ok_or_else(|| anyhow!("No cookie found"))?
        .to_str()?;

    // we split at ";" not "; ", in case the cookie is ending with ";"
    cookie_val
        .split(';')
        .find_map(|kv| {
            let index = kv.find('=')?;
            let (key, value) = kv.split_at(index);
            if key.trim() != "permission" {
                return None;
            }

            // remove the "="
            let value = value[1..].to_string();
            Some(value)
        })
        .ok_or_else(|| anyhow!("No permission found in cookie"))
}

/// Parse permission string and put permissions into a HashSet
fn permissions_into_set(p: &str) -> HashSet<&str> {
    p.split(':').filter(|s| !s.is_empty()).collect()
}