/// Cache implementation for local data storage
/// Stores agents, runs, and system status in JSON format
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentData {
    pub name: String,
    pub description: Option<String>,
    pub model: String,
    pub provider: String,
    pub created_at: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RunData {
    pub id: String,
    pub name: String,
    pub agent: String,
    pub status: String,
    pub started_at: u64,
    pub duration: Option<u64>,
    pub tokens_used: Option<u64>,
    pub cost: Option<f64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemStatusData {
    pub connected: bool,
    pub gateway_url: Option<String>,
    pub posture: String,
    pub last_updated: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CacheData {
    pub agents: Vec<AgentData>,
    pub runs: Vec<RunData>,
    pub system_status: Option<SystemStatusData>,
    pub last_updated: u64,
}

#[derive(Debug, Clone)]
pub struct Cache {
    cache_path: PathBuf,
    data: CacheData,
}

impl Cache {
    /// Create a new cache instance
    pub fn new() -> Result<Self, std::io::Error> {
        let cache_dir = Self::get_cache_dir()?;
        fs::create_dir_all(&cache_dir)?;

        let cache_path = cache_dir.join("4runr_cache.json");
        let data = Self::load_from_file(&cache_path)?;

        Ok(Self { cache_path, data })
    }

    /// Get the cache directory path
    fn get_cache_dir() -> Result<PathBuf, std::io::Error> {
        #[cfg(target_os = "windows")]
        {
            let appdata = std::env::var("APPDATA")
                .unwrap_or_else(|_| String::from("C:\\Users\\Default\\AppData\\Roaming"));
            Ok(PathBuf::from(appdata).join("4runr"))
        }

        #[cfg(not(target_os = "windows"))]
        {
            let home = std::env::var("HOME").unwrap_or_else(|_| String::from("/tmp"));
            Ok(PathBuf::from(home).join(".4runr"))
        }
    }

    /// Load cache from file
    fn load_from_file(path: &PathBuf) -> Result<CacheData, std::io::Error> {
        if !path.exists() {
            return Ok(CacheData::default());
        }

        let contents = fs::read_to_string(path)?;
        let data: CacheData = serde_json::from_str(&contents).unwrap_or_default();

        Ok(data)
    }

    /// Save cache to file
    fn save_to_file(&self) -> Result<(), std::io::Error> {
        let json = serde_json::to_string_pretty(&self.data)?;
        fs::write(&self.cache_path, json)?;
        Ok(())
    }

    /// Get current timestamp
    fn now() -> u64 {
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs()
    }

    // === Agent Operations ===

    /// Get all cached agents
    pub fn get_agents(&self) -> Vec<AgentData> {
        self.data.agents.clone()
    }

    /// Update agents cache
    pub fn update_agents(&mut self, agents: Vec<AgentData>) -> Result<(), std::io::Error> {
        self.data.agents = agents;
        self.data.last_updated = Self::now();
        self.save_to_file()
    }

    /// Add a single agent
    #[allow(dead_code)]
    pub fn add_agent(&mut self, agent: AgentData) -> Result<(), std::io::Error> {
        self.data.agents.push(agent);
        self.data.last_updated = Self::now();
        self.save_to_file()
    }

    /// Remove an agent by name
    #[allow(dead_code)]
    pub fn remove_agent(&mut self, name: &str) -> Result<(), std::io::Error> {
        self.data.agents.retain(|a| a.name != name);
        self.data.last_updated = Self::now();
        self.save_to_file()
    }

    // === Run Operations ===

    /// Get all cached runs
    #[allow(dead_code)]
    pub fn get_runs(&self) -> Vec<RunData> {
        self.data.runs.clone()
    }

    /// Update runs cache
    #[allow(dead_code)]
    pub fn update_runs(&mut self, runs: Vec<RunData>) -> Result<(), std::io::Error> {
        self.data.runs = runs;
        self.data.last_updated = Self::now();
        self.save_to_file()
    }

    /// Add a single run
    #[allow(dead_code)]
    pub fn add_run(&mut self, run: RunData) -> Result<(), std::io::Error> {
        self.data.runs.push(run);
        self.data.last_updated = Self::now();
        self.save_to_file()
    }

    /// Update a run by ID
    #[allow(dead_code)]
    pub fn update_run(&mut self, id: &str, run: RunData) -> Result<(), std::io::Error> {
        if let Some(existing) = self.data.runs.iter_mut().find(|r| r.id == id) {
            *existing = run;
            self.data.last_updated = Self::now();
            self.save_to_file()?;
        }
        Ok(())
    }

    // === System Status Operations ===

    /// Get cached system status
    pub fn get_system_status(&self) -> Option<SystemStatusData> {
        self.data.system_status.clone()
    }

    /// Update system status cache
    #[allow(dead_code)]
    pub fn update_system_status(&mut self, status: SystemStatusData) -> Result<(), std::io::Error> {
        self.data.system_status = Some(status);
        self.data.last_updated = Self::now();
        self.save_to_file()
    }

    // === Cache Management ===

    /// Get cache age in seconds
    #[allow(dead_code)]
    pub fn get_age(&self) -> u64 {
        Self::now().saturating_sub(self.data.last_updated)
    }

    /// Check if cache is stale (older than threshold)
    #[allow(dead_code)]
    pub fn is_stale(&self, threshold_secs: u64) -> bool {
        self.get_age() > threshold_secs
    }

    /// Clear all cached data
    #[allow(dead_code)]
    pub fn clear(&mut self) -> Result<(), std::io::Error> {
        self.data = CacheData::default();
        self.save_to_file()
    }

    /// Get cache data reference
    #[allow(dead_code)]
    pub fn data(&self) -> &CacheData {
        &self.data
    }
}

impl Default for Cache {
    fn default() -> Self {
        Self::new().unwrap_or_else(|_| Self {
            cache_path: PathBuf::from("4runr_cache.json"),
            data: CacheData::default(),
        })
    }
}
