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

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

use criterion::{black_box, criterion_group, criterion_main, Criterion};
use nix::unistd::Pid;
use syd::fs::{canonicalize, MissingHandling};
use tempfile::tempdir;

fn setup_paths() -> (PathBuf, PathBuf, PathBuf, PathBuf, PathBuf) {
    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(),
        non_existing_path,
        symlink_path,
        loop_symlink,
        complex_path,
    )
}

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 modes = [
        MissingHandling::Normal,
        MissingHandling::Existing,
        MissingHandling::Missing,
    ];

    for &mode in &modes {
        c.bench_function(&format!("canonicalize_complex_{mode:?}"), |b| {
            b.iter(|| {
                canonicalize(
                    pid,
                    black_box(&complex_path),
                    None,
                    black_box(mode),
                    black_box(true),
                    black_box(false),
                    black_box(false),
                    black_box(false),
                )
            })
        });

        c.bench_function(
            &format!("canonicalize_complex_{mode:?} with restrict_link=t"),
            |b| {
                b.iter(|| {
                    canonicalize(
                        pid,
                        black_box(&complex_path),
                        None,
                        black_box(mode),
                        black_box(true),
                        black_box(false),
                        black_box(false),
                        black_box(true),
                    )
                })
            },
        );

        c.bench_function(&format!("canonicalize_non_existing_{mode:?}"), |b| {
            b.iter(|| {
                canonicalize(
                    pid,
                    black_box(&non_existing),
                    None,
                    black_box(mode),
                    black_box(true),
                    black_box(false),
                    black_box(false),
                    black_box(false),
                )
            })
        });

        c.bench_function(
            &format!("canonicalize_non_existing_{mode:?} with restrict_link=t"),
            |b| {
                b.iter(|| {
                    canonicalize(
                        pid,
                        black_box(&non_existing),
                        None,
                        black_box(mode),
                        black_box(true),
                        black_box(false),
                        black_box(false),
                        black_box(true),
                    )
                })
            },
        );

        c.bench_function(&format!("canonicalize_symlink_{mode:?}"), |b| {
            b.iter(|| {
                canonicalize(
                    pid,
                    black_box(&symlink),
                    None,
                    black_box(mode),
                    black_box(true),
                    black_box(false),
                    black_box(false),
                    black_box(false),
                )
            })
        });

        c.bench_function(
            &format!("canonicalize_symlink_{mode:?} with restrict_link=1"),
            |b| {
                b.iter(|| {
                    canonicalize(
                        pid,
                        black_box(&symlink),
                        None,
                        black_box(mode),
                        black_box(true),
                        black_box(false),
                        black_box(false),
                        black_box(true),
                    )
                })
            },
        );

        c.bench_function(&format!("canonicalize_loop_{mode:?}"), |b| {
            b.iter(|| {
                canonicalize(
                    pid,
                    black_box(&loop_path),
                    None,
                    black_box(mode),
                    black_box(true),
                    black_box(false),
                    black_box(false),
                    black_box(false),
                )
            })
        });

        c.bench_function(
            &format!("canonicalize_loop_{mode:?} with restrict_link=t"),
            |b| {
                b.iter(|| {
                    canonicalize(
                        pid,
                        black_box(&loop_path),
                        None,
                        black_box(mode),
                        black_box(true),
                        black_box(false),
                        black_box(false),
                        black_box(true),
                    )
                })
            },
        );
    }

    let _ = remove_dir_all(temp_dir_path);
}

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