//! Tauri 2.x commands for OS Keychain access.
//!
//! Stores the AgentKit refresh token in the platform-native credential
//! store instead of the WebView's localStorage. The WebView on macOS
//! stores `localStorage` in a plain SQLite file under
//! `~/Library/WebKit/.../LocalStorage/` — readable by any process with
//! the same UID. The Keychain is encrypted at rest, gated by the OS.
//!
//! Mapping per platform:
//! - macOS:   Keychain Access.app
//! - Windows: Credential Manager
//! - Linux:   Secret Service (gnome-keyring / kwallet)
//!
//! Frontend contract (see `src/api/tauri-auth.ts`):
//! - `store_refresh_token(token: String)` -> Result<(), String>
//! - `load_refresh_token()`               -> Result<Option<String>, String>
//! - `clear_refresh_token()`              -> Result<(), String>
//!
//! All three commands degrade gracefully: `NoEntry` (no credential
//! stored yet) is mapped to `Ok(None)` for load and `Ok(())` for clear,
//! so first-time login and re-login do not hit errors.
//!
//! Multi-user note
//! ---------------
//! The current scheme uses a single fixed keychain entry. If a single
//! macOS user account hosts multiple AgentKit users (e.g. a shared dev
//! laptop with several logins), they share the same keychain slot —
//! last-login-wins. This matches the V1 localStorage behavior and is
//! acceptable while AgentKit is single-user-per-device. When a future
//! "user switcher" feature lands, the key will become
//! `refresh_token::<user_id>` so each user has their own slot.

use log::warn;

/// Keychain service identifier. Scoped to AgentKit (com.fischer.agentkit)
/// so it does not collide with other apps' credentials in the OS store.
const SERVICE: &str = "com.fischer.agentkit";

/// Keychain username slot. One slot per (service, username) pair; we
/// only need one for the single-refresh-token-per-device case.
const USERNAME: &str = "refresh_token";

fn entry() -> Result<keyring::Entry, String> {
    keyring::Entry::new(SERVICE, USERNAME).map_err(|e| format!("keychain init failed: {e}"))
}

/// Store (or replace) the refresh token in the OS Keychain.
///
/// The token is the only thing that needs durable storage — access
/// tokens live only in memory. See `auth.ts` for the storage strategy.
#[tauri::command]
pub async fn store_refresh_token(token: String) -> Result<(), String> {
    let entry = entry()?;
    entry
        .set_password(&token)
        .map_err(|e| format!("keychain write failed: {e}"))
}

/// Load the refresh token from the OS Keychain.
///
/// Returns `Ok(None)` when no credential has been stored (first launch
/// or after `clear_refresh_token`). This is the common case for a
/// freshly installed client and MUST NOT be reported as an error to
/// the frontend — it is the normal "log in to start" trigger.
#[tauri::command]
pub async fn load_refresh_token() -> Result<Option<String>, String> {
    let entry = entry()?;
    match entry.get_password() {
        Ok(token) => Ok(Some(token)),
        Err(keyring::Error::NoEntry) => Ok(None),
        Err(e) => Err(format!("keychain read failed: {e}")),
    }
}

/// Delete the refresh token from the OS Keychain.
///
/// Like `load_refresh_token`, `NoEntry` is mapped to `Ok(())` so
/// repeated logout / clear-from-disk operations are idempotent and
/// never surface spurious errors to the frontend.
#[tauri::command]
pub async fn clear_refresh_token() -> Result<(), String> {
    let entry = entry()?;
    match entry.delete_credential() {
        Ok(()) => Ok(()),
        Err(keyring::Error::NoEntry) => {
            warn!("clear_refresh_token: no entry to delete (already cleared)");
            Ok(())
        }
        Err(e) => Err(format!("keychain delete failed: {e}")),
    }
}

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

    /// Smoke test that the SERVICE/USERNAME constants are stable.
    /// Changing these would invalidate credentials stored on existing
    /// users' machines, so the test guards against accidental drift.
    #[test]
    fn constants_are_stable() {
        assert_eq!(SERVICE, "com.fischer.agentkit");
        assert_eq!(USERNAME, "refresh_token");
    }
}
