/*
 * Copyright Cedar Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// PANIC SAFETY: benchmarking
#![allow(clippy::unwrap_used)]
// PANIC SAFETY: benchmarking
#![allow(clippy::indexing_slicing)]

use cedar_policy::{
    Authorizer, Context, Entities, Entity, EntityUid, PolicySet, Request, RestrictedExpression,
};
use criterion::{criterion_group, criterion_main, Criterion};
use std::{
    collections::{HashMap, HashSet},
    hint::black_box,
    str::FromStr,
};

const LARGE_SIZE: usize = 100_000;
const NUM_POLICIES: usize = 10;

fn create_policy_array(rng: &mut oorandom::Rand32) -> Vec<PolicySet> {
    let mut buf = vec![];
    for _ in 0..NUM_POLICIES {
        let attribute_number = rng.rand_range(1..((LARGE_SIZE - 1) as u32));
        let policy = PolicySet::from_str(&format!(
            r#"
        permit( principal, action, resource)
        when {{ Foo::"bar".a{attribute_number} }};
    "#,
        ))
        .unwrap();
        buf.push(policy);
    }
    buf
}

fn choose<'a>(policies: &'a [PolicySet], rng: &'a mut oorandom::Rand32) -> &'a PolicySet {
    let index = rng.rand_range(0..(policies.len() as u32)) as usize;
    &policies[index]
}

pub fn large_context_record(c: &mut Criterion) {
    let mut rng = oorandom::Rand32::new(4); // chosen by fair dice role
    let large_attr = (1..=LARGE_SIZE)
        .map(|i| (format!("a{i}"), RestrictedExpression::new_bool(true)))
        .collect::<HashMap<_, _>>();
    let large_entity =
        Entity::new(r#"Foo::"bar""#.parse().unwrap(), large_attr, HashSet::new()).unwrap();

    let small_attr = [
        ("a1".to_string(), RestrictedExpression::new_bool(true)),
        ("a2".to_string(), RestrictedExpression::new_bool(true)),
    ]
    .into_iter()
    .collect::<HashMap<_, _>>();

    let small_entity =
        Entity::new(r#"Foo::"bar""#.parse().unwrap(), small_attr, HashSet::new()).unwrap();

    let euid: EntityUid = r#"Placeholder::"entity""#.parse().unwrap();
    let req = Request::new(euid.clone(), euid.clone(), euid, Context::empty(), None).unwrap();
    let large_entities = Entities::from_entities([large_entity], None).unwrap();
    let small_entities = Entities::from_entities([small_entity], None).unwrap();
    let auth = Authorizer::new();

    let mut group = c.benchmark_group("is_authorized large_entity_record");

    let policies = create_policy_array(&mut rng);

    group.bench_function("get-attr (large)", |b| {
        b.iter(|| {
            let policy = choose(&policies, &mut rng);
            auth.is_authorized(
                black_box(&req),
                black_box(policy),
                black_box(&large_entities),
            )
        })
    });

    group.bench_function("get-attr (small)", |b| {
        b.iter(|| {
            let policy = choose(&policies, &mut rng);
            auth.is_authorized(
                black_box(&req),
                black_box(policy),
                black_box(&small_entities),
            )
        })
    });

    let policy = PolicySet::from_str(
        r#"
        permit( principal, action, resource)
        when { Foo::"bar".other };
    "#,
    )
    .unwrap();
    group.bench_function("get-attr err (large)", |b| {
        b.iter(|| {
            auth.is_authorized(
                black_box(&req),
                black_box(&policy),
                black_box(&large_entities),
            )
        })
    });

    group.bench_function("get-attr err (small)", |b| {
        b.iter(|| {
            auth.is_authorized(
                black_box(&req),
                black_box(&policy),
                black_box(&small_entities),
            )
        })
    });

    group.finish();
}

criterion_group!(benches, large_context_record);
criterion_main!(benches);
