//
// Syd: rock-solid application kernel
// src/time.rs: Randomized timers
//
// Copyright (c) 2025 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::{
    fs::File,
    io::{Seek, Write},
    os::fd::OwnedFd,
    time::Instant,
};

use libseccomp::ScmpArch;
use nix::errno::Errno;

use crate::{
    config::*,
    cookie::safe_memfd_create,
    fd::seal_memfd_all,
    rng::{fillrandom, randint},
};

#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub(crate) struct sysinfo32 {
    uptime: i32,
    loads: [u32; 3],
    totalram: u32,
    freeram: u32,
    sharedram: u32,
    bufferram: u32,
    totalswap: u32,
    freeswap: u32,
    procs: u16,
    pad: u16,
    totalhigh: u32,
    freehigh: u32,
    mem_unit: u32,
    _f: [u8; 8],
}

#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub(crate) struct sysinfo64 {
    uptime: i64,
    loads: [u64; 3],
    totalram: u64,
    freeram: u64,
    sharedram: u64,
    bufferram: u64,
    totalswap: u64,
    freeswap: u64,
    procs: u16,
    pad: u16,
    totalhigh: u64,
    freehigh: u64,
    mem_unit: u32,
    _f: [u8; 0],
}

/// Randomized sysinfo(2)
pub(crate) enum SysInfo {
    S32(sysinfo32),
    S64(sysinfo64),
}

#[inline]
fn fill_pod_random<T>(pod: &mut T) -> Result<(), Errno> {
    let siz = size_of::<T>();
    let ptr = (pod as *mut T) as *mut u8;
    // SAFETY: ptr is a valid sysinfo32 or sysinfo64.
    let bytes = unsafe { std::slice::from_raw_parts_mut(ptr, siz) };
    fillrandom(bytes)
}

macro_rules! init_sysinfo {
    ($info:ident, $U:ty, $S:ty) => {{
        // randomize entire struct first
        fill_pod_random(&mut $info)?;

        // fixed fields
        $info.mem_unit = 1;
        $info.totalhigh = 0 as $U;
        $info.freehigh = 0 as $U;
        $info.totalswap = 0 as $U;
        $info.freeswap = 0 as $U;

        // realistic memory limits (unchanged)
        const MIN_RAM: $U = 0x0080_0000 as $U; // 128 MiB
        const MAX_RAM: $U = 0xFFFF_FFFF as $U; // 4 GiB

        // totalram: pow2 within [MIN_RAM, MAX_RAM]
        let mut totalram: $U = $info.totalram % (MAX_RAM - MIN_RAM + 1 as $U) + MIN_RAM;
        totalram = if totalram.is_power_of_two() {
            totalram
        } else {
            totalram.checked_next_power_of_two().unwrap_or(MAX_RAM) >> 1
        };
        totalram = totalram.clamp(MIN_RAM, MAX_RAM);
        $info.totalram = totalram;

        // freeram ≤ totalram, pow2
        let mut freeram: $U = $info.freeram % ($info.totalram + 1 as $U);
        freeram = if freeram.is_power_of_two() {
            freeram
        } else {
            freeram
                .checked_next_power_of_two()
                .unwrap_or($info.totalram)
                >> 1
        };
        $info.freeram = freeram.min($info.totalram);

        // sharedram ≤ totalram, pow2
        let mut sharedram: $U = $info.sharedram % ($info.totalram + 1 as $U);
        sharedram = if sharedram.is_power_of_two() {
            sharedram
        } else {
            sharedram
                .checked_next_power_of_two()
                .unwrap_or($info.totalram)
                >> 1
        };
        $info.sharedram = sharedram.min($info.totalram);

        // bufferram ≤ totalram, pow2
        let mut bufferram: $U = $info.bufferram % ($info.totalram + 1 as $U);
        bufferram = if bufferram.is_power_of_two() {
            bufferram
        } else {
            bufferram
                .checked_next_power_of_two()
                .unwrap_or($info.totalram)
                >> 1
        };
        $info.bufferram = bufferram.min($info.totalram);

        // uptime (same source, cast to signed kernel long width)
        #[allow(clippy::cast_possible_truncation)]
        #[allow(clippy::cast_possible_wrap)]
        {
            $info.uptime = RAND_TIMER().uptime() as $S;
        }

        // loads: fixed-point / 65536, cap at 16.0
        const LOAD_SCALE: $U = 0x1_0000 as $U;
        const MAX_LOAD: $U = (LOAD_SCALE * 16) as $U;
        for ld in &mut $info.loads {
            *ld %= MAX_LOAD + 1 as $U;
        }

        // procs in a realistic range
        const MIN_PROCS: u16 = 2;
        const MAX_PROCS: u16 = 0x8000;
        $info.procs = ($info.procs % (MAX_PROCS - MIN_PROCS + 1)) + MIN_PROCS;

        Ok::<(), Errno>(())
    }};
}

impl SysInfo {
    /// Initialize a new randomized sysinfo(2) structure.
    #[inline]
    pub fn new(arch: ScmpArch) -> Result<Self, Errno> {
        match arch {
            // 64-bit kernel long/ulong layouts
            ScmpArch::X8664
            | ScmpArch::X32 // x32 uses 64-bit __kernel_[u]long_t
            | ScmpArch::Aarch64
            | ScmpArch::Mips64
            | ScmpArch::Mipsel64
            | ScmpArch::Ppc64
            | ScmpArch::Ppc64Le
            | ScmpArch::Riscv64
            | ScmpArch::S390X
            | ScmpArch::Loongarch64 => Self::new64(),

            // 32-bit kernel long/ulong layouts
            ScmpArch::X86
            | ScmpArch::Arm
            | ScmpArch::M68k
            | ScmpArch::Mips
            | ScmpArch::Mipsel
            | ScmpArch::Mips64N32     // ILP32 on 64-bit mips
            | ScmpArch::Mipsel64N32   // ILP32 on 64-bit mips (little-endian)
            | ScmpArch::S390          // 31-bit compat personality
            => Self::new32(),
            _ => Err(Errno::ENOSYS),
        }
    }

    /// Initialize a new randomized sysinfo(2) structure for 32-bit.
    pub(crate) fn new32() -> Result<Self, Errno> {
        // SAFETY: sysinfo32 is repr(C) and zero-initialization is valid for this type.
        let mut info: sysinfo32 = unsafe { std::mem::zeroed() };
        init_sysinfo!(info, u32, i32)?;
        Ok(SysInfo::S32(info))
    }

    /// Initialize a new randomized sysinfo(2) structure for 64-bit.
    pub fn new64() -> Result<Self, Errno> {
        // SAFETY: sysinfo64 is repr(C) and zero-initialization is valid for this type.
        let mut info: sysinfo64 = unsafe { std::mem::zeroed() };
        init_sysinfo!(info, u64, i64)?;
        Ok(SysInfo::S64(info))
    }

    #[inline]
    pub fn as_bytes(&self) -> &[u8] {
        match self {
            // SAFETY: s is a valid sysinfo32 and we only read its memory as bytes.
            Self::S32(s) => unsafe {
                std::slice::from_raw_parts((s as *const _) as *const u8, size_of::<sysinfo32>())
            },
            // SAFETY: s is a valid sysinfo64 and we only read its memory as bytes.
            Self::S64(s) => unsafe {
                std::slice::from_raw_parts((s as *const _) as *const u8, size_of::<sysinfo64>())
            },
        }
    }
}

/// A cryptographically wrapped monotonic timer that masks both uptime
/// and idle time with independent realistic offsets.
pub struct RandTimer {
    /// Monotonic reference point for both fields.
    pub start: Instant,
    /// 64-bit random offset for reported uptime.
    pub uptime_offset: u64,
    /// 64-bit random offset for reported idle time.
    pub idle_offset: u64,
}

impl RandTimer {
    /// Generates a new timer with random offsets for uptime and idle.
    ///
    /// If `timens` is `true`, offsets are zeroed.
    pub fn new(timens: bool) -> Result<Self, Errno> {
        // Fill both buffers with cryptographically secure bytes.
        // Use a plausible max offset ~194 days (0xFF_FFFF ≈ 16.7 million seconds).
        // This yields realistic yet unpredictable uptime/idle metrics.
        Ok(Self {
            start: Instant::now(),
            uptime_offset: if timens { 0 } else { randint(1..=0xFF_FFFF)? },
            idle_offset: if timens { 0 } else { randint(1..=0xFF_FFFF)? },
        })
    }

    /// Returns a masked uptime in seconds, wrapping on overflow.
    pub fn uptime(&self) -> u64 {
        let elapsed = self.start.elapsed().as_secs();
        elapsed.wrapping_add(self.uptime_offset)
    }

    /// Returns a masked idle time in seconds, wrapping on overflow.
    pub fn idle(&self) -> u64 {
        let elapsed = self.start.elapsed().as_secs();
        elapsed.wrapping_add(self.idle_offset)
    }

    /// Returns /proc/uptime compatible String representation.
    pub fn proc(&self) -> String {
        let elapsed = self.start.elapsed().as_secs();
        format!(
            "{}.{:02} {}.{:02}\n",
            elapsed.wrapping_add(self.uptime_offset),
            self.uptime_offset % 100,
            elapsed.wrapping_add(self.idle_offset),
            self.idle_offset % 100
        )
    }

    /// Returns a /proc/uptime compatible memory file descriptor.
    ///
    /// The memory file descriptor is write-sealed.
    /// The memory file descriptor is exec-sealed on Linux>=6.3.
    pub fn proc_fd(&self) -> Result<OwnedFd, Errno> {
        let repr = self.proc();
        let data = repr.as_bytes();

        let fd = safe_memfd_create(c"syd/proc/uptime", *SAFE_MFD_FLAGS)?;
        let mut file = File::from(fd);

        file.write_all(data).or(Err(Errno::EIO))?;
        file.rewind().or(Err(Errno::EIO))?;

        // SAFETY: Deny further writes to the file descriptor.
        seal_memfd_all(&file)?;

        Ok(file.into())
    }
}

#[cfg(test)]
mod tests {
    use std::{thread, time::Duration};

    use super::RandTimer;

    /// Basic creation test: ensures RandTimer can be constructed and used.
    #[test]
    fn test_basic_creation() {
        let rt = RandTimer::new(false).expect("RandTimer creation failed");
        let _ = rt.uptime();
        let _ = rt.idle();
    }

    /// Verifies monotonic behavior over a short sleep for both uptime and idle.
    #[test]
    fn test_monotonic_increase() {
        let rt = RandTimer::new(false).expect("RandTimer creation failed");
        let before_uptime = rt.uptime();
        let before_idle = rt.idle();
        thread::sleep(Duration::from_millis(10));
        let after_uptime = rt.uptime();
        let after_idle = rt.idle();
        assert!(
            after_uptime >= before_uptime,
            "Uptime decreased from {} to {}",
            before_uptime,
            after_uptime
        );
        assert!(
            after_idle >= before_idle,
            "Idle time decreased from {} to {}",
            before_idle,
            after_idle
        );
    }

    /// Fires many calls to `uptime` and `idle` in quick succession.
    #[test]
    fn test_rapid_fire() {
        let rt = RandTimer::new(false).expect("RandTimer creation failed");
        for _ in 0..10_000 {
            let _ = rt.uptime();
            let _ = rt.idle();
        }
    }

    /// Stress-test repeated creation of RandTimer objects.
    #[test]
    fn test_repeated_creation() {
        for _ in 0..1000 {
            let rt = RandTimer::new(false).expect("RandTimer creation failed");
            assert_ne!(
                rt.uptime(),
                0,
                "Uptime offset might be zero too often, suspicious RNG."
            );
            assert_ne!(
                rt.idle(),
                0,
                "Idle offset might be zero too often, suspicious RNG."
            );
        }
    }

    /// Concurrency test: multiple threads each create + use RandTimer heavily.
    #[test]
    fn test_concurrency() {
        let threads = 8;
        let iterations = 2000;
        let mut handles = Vec::new();
        for _ in 0..threads {
            handles.push(thread::spawn(move || {
                for _ in 0..iterations {
                    let rt = RandTimer::new(false).unwrap();
                    let _ = rt.uptime();
                    let _ = rt.idle();
                }
            }));
        }
        for handle in handles {
            handle.join().expect("Thread panic in concurrency test");
        }
    }

    /// Ensures uptime offsets vary across multiple RandTimers. Checks for suspicious uniform offsets.
    #[test]
    fn test_uptime_offset_variability() {
        let iterations = 30;
        let mut offsets = Vec::new();
        for _ in 0..iterations {
            let rt = RandTimer::new(false).expect("RandTimer creation failed");
            // "Peek" uptime offset by subtracting the measured elapsed from `uptime`.
            let elapsed = rt.start.elapsed().as_secs();
            let offset_guess = rt.uptime().wrapping_sub(elapsed);
            offsets.push(offset_guess);
        }
        let all_same = offsets.windows(2).all(|w| w[0] == w[1]);
        assert!(
            !all_same,
            "All uptime offsets identical over {} RandTimer creations, suspicious RNG!",
            iterations
        );
    }

    /// Ensures idle offsets vary across multiple RandTimers.
    #[test]
    fn test_idle_offset_variability() {
        let iterations = 30;
        let mut offsets = Vec::new();
        for _ in 0..iterations {
            let rt = RandTimer::new(false).expect("RandTimer creation failed");
            let elapsed = rt.start.elapsed().as_secs();
            let offset_guess = rt.idle().wrapping_sub(elapsed);
            offsets.push(offset_guess);
        }
        let all_same = offsets.windows(2).all(|w| w[0] == w[1]);
        assert!(!all_same, "All idle offsets identical, suspicious RNG!");
    }

    /// Test artificially forcing uptime offset near u64::MAX to see if wrapping works.
    #[test]
    fn test_uptime_wrapping() {
        let mut rt = RandTimer::new(false).expect("RandTimer creation failed");
        rt.uptime_offset = u64::MAX - 1;
        let before = rt.uptime();
        thread::sleep(Duration::from_secs(1));
        let after = rt.uptime();
        assert!(
            after != before,
            "No change in uptime after forcing near-max offset + 1s sleep!"
        );
    }

    /// Test artificially forcing idle offset near u64::MAX to see if wrapping works.
    #[test]
    fn test_idle_wrapping() {
        let mut rt = RandTimer::new(false).expect("RandTimer creation failed");
        rt.idle_offset = u64::MAX - 1;
        let before = rt.idle();
        thread::sleep(Duration::from_secs(1));
        let after = rt.idle();
        assert!(
            after != before,
            "No change in idle time after forcing near-max offset + 1s sleep!"
        );
    }

    /// Check forcibly set offsets to zero for near "raw monotonic" behavior.
    #[test]
    fn test_force_offsets_zero() {
        let mut rt = RandTimer::new(false).expect("RandTimer creation failed");
        rt.uptime_offset = 0;
        rt.idle_offset = 0;
        let t1_up = rt.uptime();
        let t1_idle = rt.idle();
        thread::sleep(Duration::from_millis(5));
        let t2_up = rt.uptime();
        let t2_idle = rt.idle();
        assert!(
            t2_up >= t1_up,
            "Uptime decreased with zero offset: {} to {}",
            t1_up,
            t2_up
        );
        assert!(
            t2_idle >= t1_idle,
            "Idle decreased with zero offset: {} to {}",
            t1_idle,
            t2_idle
        );
    }

    /// Force a very large idle offset and a small sleep. Ensures no panic or freeze.
    #[test]
    fn test_large_idle_offset_small_sleep() {
        let mut rt = RandTimer::new(false).expect("RandTimer creation failed");
        rt.idle_offset = u64::MAX / 2;
        let before = rt.idle();
        thread::sleep(Duration::from_secs(1));
        let after = rt.idle();
        assert_ne!(
            before, after,
            "Idle unchanged after short sleep with large offset!"
        );
    }

    /// Big loop creation test for both offsets.
    #[test]
    fn test_big_loop_creation() {
        for i in 0..10_000 {
            let rt = RandTimer::new(false).expect("RandTimer creation failed");
            if i % 1000 == 0 {
                let _ = rt.uptime();
                let _ = rt.idle();
            }
        }
    }

    /// Force multiple odd offsets for both uptime and idle, ensure each acts consistently.
    #[test]
    fn test_various_forced_offsets() {
        let test_offsets = [
            (1, 1),
            (42, 999_999_999),
            (0x0000FFFF_FFFFFFFF, 0xFFFFAAAA_FFFFFFFF),
            (0xFFFFFFFF_FFFFFFFF, 0x55555555_55555555),
        ];
        for &(u_off, i_off) in &test_offsets {
            let mut rt = RandTimer::new(false).expect("RandTimer creation failed");
            rt.uptime_offset = u_off;
            rt.idle_offset = i_off;
            let up1 = rt.uptime();
            let idle1 = rt.idle();
            thread::sleep(Duration::from_millis(2));
            let up2 = rt.uptime();
            let idle2 = rt.idle();
            assert!(
                up2 >= up1,
                "Uptime offset {} yields invalid progression: {} -> {}",
                u_off,
                up1,
                up2
            );
            assert!(
                idle2 >= idle1,
                "Idle offset {} yields invalid progression: {} -> {}",
                i_off,
                idle1,
                idle2
            );
        }
    }
}
