#![deny(warnings)]
#![allow(dead_code)]
//! [TSM.ID].[11031972] -- Platform X Ecosystem
//! xcu-valkyrie -- Process Lifecycle Manager (OOM Killer Cerdas)
//! Prioritized process termination under memory pressure

use std::collections::BinaryHeap;
use std::cmp::Ordering;

#[derive(Debug)]
pub enum ValkyrieError {
    NoProcesses(String),
    ProtectedProcess(String),
    KillFailed(String),
}

impl std::fmt::Display for ValkyrieError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::NoProcesses(e) => write!(f, "No processes: {e}"),
            Self::ProtectedProcess(e) => write!(f, "Protected: {e}"),
            Self::KillFailed(e) => write!(f, "Kill failed: {e}"),
        }
    }
}

impl std::error::Error for ValkyrieError {}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcessPriority {
    Critical,   // Never kill (xcu-core, database)
    High,       // Kill last (auth, routing)
    Normal,     // Kill if needed (workers)
    Low,        // Kill first (background, cache)
    Expendable, // Kill immediately (temp, preview)
}

impl ProcessPriority {
    fn weight(&self) -> u32 {
        match self {
            Self::Critical => 0,
            Self::High => 1,
            Self::Normal => 2,
            Self::Low => 3,
            Self::Expendable => 4,
        }
    }
}

#[derive(Debug, Clone)]
pub struct ProcessInfo {
    pub pid: u32,
    pub name: String,
    pub memory_mb: u64,
    pub cpu_percent: f64,
    pub priority: ProcessPriority,
    pub uptime_secs: u64,
    pub restarts: u32,
}

/// Kill score — higher = should be killed first
#[derive(Debug, Clone)]
struct KillCandidate {
    pid: u32,
    name: String,
    score: f64,
    memory_mb: u64,
}

impl PartialEq for KillCandidate {
    fn eq(&self, other: &Self) -> bool { self.score == other.score }
}
impl Eq for KillCandidate {}
impl PartialOrd for KillCandidate {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
impl Ord for KillCandidate {
    fn cmp(&self, other: &Self) -> Ordering {
        self.score.partial_cmp(&other.score).unwrap_or(Ordering::Equal)
    }
}

pub struct Valkyrie {
    memory_pressure_threshold_percent: f64,
    protected_names: Vec<String>,
}

impl Valkyrie {
    pub fn new(threshold: f64, protected: Vec<String>) -> Self {
        Self {
            memory_pressure_threshold_percent: threshold,
            protected_names: protected,
        }
    }

    /// Calculate kill score for a process
    /// Higher score = more likely to be killed
    fn calculate_kill_score(&self, proc: &ProcessInfo) -> f64 {
        let priority_weight = proc.priority.weight() as f64 * 25.0;
        let memory_weight = proc.memory_mb as f64 * 0.1;
        let cpu_weight = proc.cpu_percent * 0.5;
        let restart_penalty = proc.restarts as f64 * 5.0; // Often crashing = kill first
        let uptime_bonus = (proc.uptime_secs as f64 / 3600.0).min(10.0); // Long-running = keep

        priority_weight + memory_weight + cpu_weight + restart_penalty - uptime_bonus
    }

    /// Choose processes to kill to free target_mb of memory
    pub fn choose_victims(
        &self,
        processes: &[ProcessInfo],
        target_free_mb: u64,
    ) -> Result<Vec<(u32, String, u64)>, ValkyrieError> {
        let mut heap = BinaryHeap::new();

        for proc in processes {
            // Skip critical and protected
            if proc.priority == ProcessPriority::Critical {
                continue;
            }
            if self.protected_names.iter().any(|n| proc.name.contains(n)) {
                continue;
            }

            let score = self.calculate_kill_score(proc);
            heap.push(KillCandidate {
                pid: proc.pid,
                name: proc.name.clone(),
                score,
                memory_mb: proc.memory_mb,
            });
        }

        if heap.is_empty() {
            return Err(ValkyrieError::NoProcesses("No killable processes".into()));
        }

        let mut victims = Vec::new();
        let mut freed: u64 = 0;

        while let Some(candidate) = heap.pop() {
            victims.push((candidate.pid, candidate.name, candidate.memory_mb));
            freed += candidate.memory_mb;
            if freed >= target_free_mb {
                break;
            }
        }

        Ok(victims)
    }

    /// Check if memory pressure requires action
    pub fn check_pressure(
        &self,
        used_mb: u64,
        total_mb: u64,
    ) -> Result<Option<u64>, ValkyrieError> {
        if total_mb == 0 {
            return Err(ValkyrieError::KillFailed("Total memory is 0".into()));
        }
        let percent = (used_mb as f64 / total_mb as f64) * 100.0;
        if percent >= self.memory_pressure_threshold_percent {
            let target = used_mb - (total_mb as f64 * 0.7) as u64;
            Ok(Some(target))
        } else {
            Ok(None)
        }
    }

    /// Full analysis: detect pressure → choose victims → return kill list
    pub fn analyze_and_recommend(
        &self,
        processes: &[ProcessInfo],
        used_mb: u64,
        total_mb: u64,
    ) -> Result<Vec<(u32, String, u64)>, ValkyrieError> {
        match self.check_pressure(used_mb, total_mb)? {
            Some(target) => self.choose_victims(processes, target),
            None => Ok(Vec::new()), // No pressure
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn make_procs() -> Vec<ProcessInfo> {
        vec![
            ProcessInfo { pid: 1, name: "xcu-core".into(), memory_mb: 200, cpu_percent: 5.0, priority: ProcessPriority::Critical, uptime_secs: 86400, restarts: 0 },
            ProcessInfo { pid: 2, name: "cache-worker".into(), memory_mb: 500, cpu_percent: 2.0, priority: ProcessPriority::Low, uptime_secs: 3600, restarts: 0 },
            ProcessInfo { pid: 3, name: "preview-gen".into(), memory_mb: 300, cpu_percent: 80.0, priority: ProcessPriority::Expendable, uptime_secs: 60, restarts: 5 },
            ProcessInfo { pid: 4, name: "auth-service".into(), memory_mb: 100, cpu_percent: 1.0, priority: ProcessPriority::High, uptime_secs: 43200, restarts: 0 },
        ]
    }

    #[test]
    fn test_never_kill_critical() {
        let v = Valkyrie::new(80.0, vec![]);
        let victims = v.choose_victims(&make_procs(), 1000).unwrap();
        assert!(victims.iter().all(|(pid, _, _)| *pid != 1));
    }

    #[test]
    fn test_kill_expendable_first() {
        let v = Valkyrie::new(80.0, vec![]);
        let victims = v.choose_victims(&make_procs(), 100).unwrap();
        assert_eq!(victims[0].0, 3); // preview-gen (expendable, high CPU, many restarts)
    }

    #[test]
    fn test_no_pressure() {
        let v = Valkyrie::new(80.0, vec![]);
        let result = v.analyze_and_recommend(&make_procs(), 4000, 8000).unwrap();
        assert!(result.is_empty());
    }

    #[test]
    fn test_under_pressure() {
        let v = Valkyrie::new(80.0, vec![]);
        let result = v.analyze_and_recommend(&make_procs(), 7000, 8000).unwrap();
        assert!(!result.is_empty());
    }
}
