use serde_json::json;
use std::path::Path;
use std::time::SystemTime;

use crate::state::{atomic_write_json, load_json_with_fallbacks, BACKUP_COUNT};
use crate::utils::deduplicate_list;

/// Maximum number of recent folders to keep.
pub const RECENTS_MAX: usize = 50;

/// Name of the recents file within the state directory.
const RECENTS_FILENAME: &str = "recent_folders.json";

/// Load the list of recently opened folders.
///
/// Reads from `{state_dir}/recent_folders.json`. Returns an empty vec if the
/// file is missing, corrupt, or has an unexpected shape.
pub fn load_recents(state_dir: &Path) -> Vec<String> {
    let path = state_dir.join(RECENTS_FILENAME);
    let data = match load_json_with_fallbacks(&path, BACKUP_COUNT) {
        Some(v) => v,
        None => return Vec::new(),
    };

    // Top-level must be an object
    let obj = match data.as_object() {
        Some(o) => o,
        None => return Vec::new(),
    };

    // "items" must be an array
    let items_val = match obj.get("items") {
        Some(v) => v,
        None => return Vec::new(),
    };
    let arr = match items_val.as_array() {
        Some(a) => a,
        None => return Vec::new(),
    };

    // Collect only string elements
    let strings: Vec<String> = arr
        .iter()
        .filter_map(|v| v.as_str().map(String::from))
        .collect();

    let deduped = deduplicate_list(&strings);
    deduped.into_iter().take(RECENTS_MAX).collect()
}

/// Save the list of recently opened folders.
///
/// Deduplicates and truncates to `RECENTS_MAX`, then writes JSON with version
/// and timestamp metadata.
pub fn save_recents(state_dir: &Path, paths: &[String]) {
    let cleaned: Vec<String> = deduplicate_list(paths)
        .into_iter()
        .take(RECENTS_MAX)
        .collect();

    let timestamp = SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)
        .map(|d| d.as_secs())
        .unwrap_or(0);

    let data = json!({
        "version": 2,
        "updated_at": timestamp,
        "items": cleaned,
    });

    let path = state_dir.join(RECENTS_FILENAME);
    atomic_write_json(&path, &data, BACKUP_COUNT);
}

/// Add a folder to the top of the recent folders list.
///
/// If the path already exists in the list it is moved to the front rather than
/// duplicated.
pub fn push_recent(state_dir: &Path, path_str: &str) {
    let mut paths = load_recents(state_dir);
    let trimmed = path_str.trim().to_string();
    paths.retain(|p| p != &trimmed);
    paths.insert(0, trimmed);
    save_recents(state_dir, &paths);
}

/// Remove a specific path from the recent folders list and save.
pub fn remove_recent(state_dir: &Path, path_str: &str) {
    let mut paths = load_recents(state_dir);
    let trimmed = path_str.trim().to_string();
    paths.retain(|p| p != &trimmed);
    save_recents(state_dir, &paths);
}

// ===========================================================================
// Tests
// ===========================================================================

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

    #[test]
    fn test_load_empty_returns_empty_vec() {
        let dir = TempDir::new().unwrap();
        let result = load_recents(dir.path());
        assert!(result.is_empty());
    }

    #[test]
    fn test_push_adds_to_front() {
        let dir = TempDir::new().unwrap();
        push_recent(dir.path(), "/folder/a");
        push_recent(dir.path(), "/folder/b");
        push_recent(dir.path(), "/folder/c");

        let recents = load_recents(dir.path());
        assert_eq!(recents[0], "/folder/c");
        assert_eq!(recents[1], "/folder/b");
        assert_eq!(recents[2], "/folder/a");
    }

    #[test]
    fn test_push_deduplicates() {
        let dir = TempDir::new().unwrap();
        push_recent(dir.path(), "/folder/a");
        push_recent(dir.path(), "/folder/b");
        push_recent(dir.path(), "/folder/a"); // duplicate — should move to front

        let recents = load_recents(dir.path());
        assert_eq!(recents.len(), 2);
        assert_eq!(recents[0], "/folder/a");
        assert_eq!(recents[1], "/folder/b");
    }

    #[test]
    fn test_remove_works() {
        let dir = TempDir::new().unwrap();
        push_recent(dir.path(), "/folder/a");
        push_recent(dir.path(), "/folder/b");
        push_recent(dir.path(), "/folder/c");

        remove_recent(dir.path(), "/folder/b");

        let recents = load_recents(dir.path());
        assert_eq!(recents.len(), 2);
        assert_eq!(recents[0], "/folder/c");
        assert_eq!(recents[1], "/folder/a");
    }

    #[test]
    fn test_remove_nonexistent_is_noop() {
        let dir = TempDir::new().unwrap();
        push_recent(dir.path(), "/folder/a");
        remove_recent(dir.path(), "/folder/zzz");

        let recents = load_recents(dir.path());
        assert_eq!(recents.len(), 1);
        assert_eq!(recents[0], "/folder/a");
    }

    #[test]
    fn test_max_50_limit() {
        let dir = TempDir::new().unwrap();

        // Push 55 items
        for i in 0..55 {
            push_recent(dir.path(), &format!("/folder/{}", i));
        }

        let recents = load_recents(dir.path());
        assert_eq!(recents.len(), RECENTS_MAX);
        // Most recent should be at front
        assert_eq!(recents[0], "/folder/54");
    }

    #[test]
    fn test_save_and_load_round_trip() {
        let dir = TempDir::new().unwrap();
        let paths: Vec<String> = vec![
            "/home/user/videos".to_string(),
            "/home/user/tutorials".to_string(),
        ];
        save_recents(dir.path(), &paths);

        let loaded = load_recents(dir.path());
        assert_eq!(loaded, paths);
    }

    #[test]
    fn test_load_non_object_returns_empty() {
        let dir = TempDir::new().unwrap();
        let path = dir.path().join(RECENTS_FILENAME);
        // Write a JSON array instead of an object
        std::fs::write(&path, "[1, 2, 3]").unwrap();

        let result = load_recents(dir.path());
        assert!(result.is_empty());
    }

    #[test]
    fn test_load_missing_items_key_returns_empty() {
        let dir = TempDir::new().unwrap();
        let path = dir.path().join(RECENTS_FILENAME);
        std::fs::write(&path, r#"{"version": 2}"#).unwrap();

        let result = load_recents(dir.path());
        assert!(result.is_empty());
    }

    #[test]
    fn test_load_items_not_array_returns_empty() {
        let dir = TempDir::new().unwrap();
        let path = dir.path().join(RECENTS_FILENAME);
        std::fs::write(&path, r#"{"items": "not an array"}"#).unwrap();

        let result = load_recents(dir.path());
        assert!(result.is_empty());
    }
}
