//
// Syd: rock-solid application kernel
// benches/canonicalize.rs: Benchmarks for syd::fs::safe_canonicalize()
//
// Copyright (c) 2023, 2024 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::{
    fs::{create_dir_all, remove_dir_all, File},
    os::unix::fs::symlink,
};

use criterion::{black_box, criterion_group, criterion_main, Criterion};
use nix::unistd::Pid;
use syd::{
    fs::{safe_canonicalize, FsFlags},
    path::XPathBuf,
    sandbox::Flags,
};
use tempfile::tempdir;

fn setup_paths() -> (XPathBuf, XPathBuf, XPathBuf, XPathBuf, XPathBuf) {
    let temp_dir = tempdir().expect("Failed to create a temp dir");
    let temp_dir_path = temp_dir.path();

    // Existing path with symlinks
    let existing_path = temp_dir_path.join("existing");
    create_dir_all(&existing_path).expect("Failed to create existing path");
    let symlink_path = temp_dir_path.join("symlink");
    symlink(&existing_path, &symlink_path).expect("Failed to create symlink");

    // Self-referencing loop
    let loop_path = temp_dir_path.join("loop");
    create_dir_all(&loop_path).expect("Failed to create loop path");
    let loop_symlink = loop_path.join("self_loop");
    symlink(&loop_path, &loop_symlink).expect("Failed to create self-referencing symlink");

    // Non-existing path
    let non_existing_path = temp_dir_path.join("non_existing");

    // Complex structure setup
    let complex_base = temp_dir_path.join("syd-test");
    let complex_a = complex_base.join("a");
    let complex_1 = complex_base.join("1");
    let complex_target = complex_a.join("target.txt");
    let complex_link_to_a = complex_1.join("2/3/link_to_a");
    let complex_link_to_1 = complex_a.join("b/c/d/link_to_1");
    let complex_link_to_c = complex_1.join("2/3/link_to_c");

    create_dir_all(complex_a.join("b/c/d/e/f"))
        .expect("Failed to create complex a directory structure");
    create_dir_all(complex_1.join("2/3")).expect("Failed to create complex 1 directory structure");
    File::create(&complex_target).expect("Failed to create target file");

    symlink("../../../a", &complex_link_to_a).expect("Failed to create symlink to a");
    symlink("../../../../1", &complex_link_to_1).expect("Failed to create symlink to 1");
    symlink("../../../a/b/c", &complex_link_to_c).expect("Failed to create symlink to c");

    let complex_path = complex_link_to_a.join("b/c/d/e/f/../../../../../b/c/d/link_to_1/../../syd-test/1/2/3/link_to_c/d/e/f/../../link_to_1/2/../././../a/.././a/target.txt");

    (
        temp_dir_path.to_path_buf().into(),
        non_existing_path.into(),
        symlink_path.into(),
        loop_symlink.into(),
        complex_path.into(),
    )
}

pub fn canonicalize_benchmark(c: &mut Criterion) {
    let (temp_dir_path, non_existing, symlink, loop_path, complex_path) = setup_paths();

    let pid = Pid::this();
    let options = [FsFlags::empty(), FsFlags::MUST_PATH, FsFlags::MISS_LAST];

    // Init preopen FDs that canonicalize expects.
    syd::config::proc_init().unwrap();

    for &option in &options {
        c.bench_function(&format!("safe_canonicalize_complex_{option:?}"), |b| {
            b.iter(|| {
                safe_canonicalize(
                    pid,
                    Some(libc::AT_FDCWD),
                    black_box(&complex_path),
                    black_box(option),
                    Flags::FL_ALLOW_UNSAFE_MAGICLINKS,
                )
            })
        });

        c.bench_function(
            &format!("safe_canonicalize_complex_{option:?} with restrict_link=t"),
            |b| {
                b.iter(|| {
                    safe_canonicalize(
                        pid,
                        Some(libc::AT_FDCWD),
                        black_box(&complex_path),
                        black_box(option),
                        Flags::empty(),
                    )
                })
            },
        );

        c.bench_function(&format!("safe_canonicalize_non_existing_{option:?}"), |b| {
            b.iter(|| {
                safe_canonicalize(
                    pid,
                    Some(libc::AT_FDCWD),
                    black_box(&non_existing),
                    black_box(option),
                    Flags::empty(),
                )
            })
        });

        c.bench_function(
            &format!("safe_canonicalize_non_existing_{option:?} with restrict_link=t"),
            |b| {
                b.iter(|| {
                    safe_canonicalize(
                        pid,
                        Some(libc::AT_FDCWD),
                        black_box(&non_existing),
                        black_box(option),
                        Flags::empty(),
                    )
                })
            },
        );

        c.bench_function(&format!("safe_canonicalize_symlink_{option:?}"), |b| {
            b.iter(|| {
                safe_canonicalize(
                    pid,
                    Some(libc::AT_FDCWD),
                    black_box(&symlink),
                    black_box(option),
                    Flags::FL_ALLOW_UNSAFE_MAGICLINKS,
                )
            })
        });

        c.bench_function(
            &format!("safe_canonicalize_symlink_{option:?} with restrict_link=1"),
            |b| {
                b.iter(|| {
                    safe_canonicalize(
                        pid,
                        Some(libc::AT_FDCWD),
                        black_box(&symlink),
                        black_box(option),
                        Flags::empty(),
                    )
                })
            },
        );

        c.bench_function(&format!("safe_canonicalize_loop_{option:?}"), |b| {
            b.iter(|| {
                safe_canonicalize(
                    pid,
                    Some(libc::AT_FDCWD),
                    black_box(&loop_path),
                    black_box(option),
                    Flags::FL_ALLOW_UNSAFE_MAGICLINKS,
                )
            })
        });

        c.bench_function(
            &format!("safe_canonicalize_loop_{option:?} with restrict_link=t"),
            |b| {
                b.iter(|| {
                    safe_canonicalize(
                        pid,
                        Some(libc::AT_FDCWD),
                        black_box(&loop_path),
                        black_box(option),
                        Flags::empty(),
                    )
                })
            },
        );
    }

    let _ = remove_dir_all(temp_dir_path);
}

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