// ─── Me Cursor Persistence ──────────────────────────────────────────────────
//
// File-based cursor for the "since last check" section of `lore me`.
// Stores per-user timestamps in ~/.local/share/lore/me_cursor_<username>.json.

use std::io;
use std::io::Write;

use serde::{Deserialize, Serialize};

use super::paths::get_cursor_path;

#[derive(Serialize, Deserialize)]
struct CursorFile {
    last_check_ms: i64,
}

/// Read the last-check cursor. Returns `None` if the file doesn't exist or is corrupt.
pub fn read_cursor(username: &str) -> Option<i64> {
    let path = get_cursor_path(username);
    let data = std::fs::read_to_string(path).ok()?;
    let cursor: CursorFile = serde_json::from_str(&data).ok()?;
    Some(cursor.last_check_ms)
}

/// Write the last-check cursor atomically.
pub fn write_cursor(username: &str, timestamp_ms: i64) -> io::Result<()> {
    let path = get_cursor_path(username);
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)?;
        let cursor = CursorFile {
            last_check_ms: timestamp_ms,
        };
        let json = serde_json::to_string(&cursor).map_err(io::Error::other)?;
        let nonce = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|d| d.as_nanos())
            .unwrap_or(0);
        let file_name = path
            .file_name()
            .and_then(|name| name.to_str())
            .unwrap_or("me_cursor.json");
        let temp_path = parent.join(format!(".{file_name}.{nonce}.tmp"));
        {
            let mut temp_file = std::fs::File::create(&temp_path)?;
            temp_file.write_all(json.as_bytes())?;
            temp_file.sync_all()?;
        }
        std::fs::rename(&temp_path, &path)?;
        return Ok(());
    }

    Err(io::Error::new(
        io::ErrorKind::InvalidInput,
        "cursor path has no parent directory",
    ))
}

/// Reset the cursor by deleting the file. No-op if it doesn't exist.
pub fn reset_cursor(username: &str) -> io::Result<()> {
    let path = get_cursor_path(username);
    match std::fs::remove_file(path) {
        Ok(()) => Ok(()),
        Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
        Err(e) => Err(e),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::{Mutex, OnceLock};

    fn env_lock() -> &'static Mutex<()> {
        static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
        LOCK.get_or_init(|| Mutex::new(()))
    }

    fn with_temp_xdg_data_home<T>(f: impl FnOnce() -> T) -> T {
        let _guard = env_lock().lock().unwrap();
        let previous = std::env::var_os("XDG_DATA_HOME");
        let dir = tempfile::tempdir().unwrap();
        // SAFETY: test-only scoped env override.
        unsafe { std::env::set_var("XDG_DATA_HOME", dir.path()) };
        let result = f();
        match previous {
            Some(value) => {
                // SAFETY: restoring prior environment for test isolation.
                unsafe { std::env::set_var("XDG_DATA_HOME", value) };
            }
            None => {
                // SAFETY: restoring prior environment for test isolation.
                unsafe { std::env::remove_var("XDG_DATA_HOME") };
            }
        }
        result
    }

    #[test]
    fn read_cursor_returns_none_when_missing() {
        with_temp_xdg_data_home(|| {
            assert_eq!(read_cursor("alice"), None);
        });
    }

    #[test]
    fn cursor_roundtrip() {
        with_temp_xdg_data_home(|| {
            write_cursor("alice", 1_700_000_000_000).unwrap();
            assert_eq!(read_cursor("alice"), Some(1_700_000_000_000));
        });
    }

    #[test]
    fn cursor_isolated_per_user() {
        with_temp_xdg_data_home(|| {
            write_cursor("alice", 100).unwrap();
            write_cursor("bob", 200).unwrap();
            assert_eq!(read_cursor("alice"), Some(100));
            assert_eq!(read_cursor("bob"), Some(200));
        });
    }

    #[test]
    fn reset_cursor_only_affects_target_user() {
        with_temp_xdg_data_home(|| {
            write_cursor("alice", 100).unwrap();
            write_cursor("bob", 200).unwrap();
            reset_cursor("alice").unwrap();
            assert_eq!(read_cursor("alice"), None);
            assert_eq!(read_cursor("bob"), Some(200));
        });
    }

    #[test]
    fn cursor_write_keeps_valid_json() {
        with_temp_xdg_data_home(|| {
            write_cursor("alice", 111).unwrap();
            write_cursor("alice", 222).unwrap();
            let data = std::fs::read_to_string(get_cursor_path("alice")).unwrap();
            let parsed: CursorFile = serde_json::from_str(&data).unwrap();
            assert_eq!(parsed.last_check_ms, 222);
        });
    }

    #[test]
    fn parse_corrupt_json_returns_none() {
        let bad_json = "not json at all";
        let parsed: Option<CursorFile> = serde_json::from_str(bad_json).ok();
        assert!(parsed.is_none());
    }
}
