use anyhow::{Context, Result};

use super::types::ExternalSystem;

const KEYRING_SERVICE: &str = "furnace-sync";

/// Credential store backed exclusively by the OS keyring.
///
/// Uses macOS Keychain, Windows Credential Manager, or Linux secret-service
/// via the `keyring` crate. No in-memory storage — tokens are always persisted
/// securely in the OS credential store.
#[derive(Debug, Clone)]
pub struct CredentialStore;

impl CredentialStore {
    /// Create a new credential store.
    pub fn new() -> Self {
        Self
    }

    /// Return the keyring key for a given external system.
    ///
    /// Format: `"furnace-sync:{system}"` where `{system}` is the lowercase
    /// display name (e.g. `"furnace-sync:linear"`).
    pub fn token_ref_key(system: &ExternalSystem) -> String {
        format!("{}:{}", KEYRING_SERVICE, system.to_string().to_lowercase())
    }

    /// Get a keyring entry for the given system.
    fn entry(system: &ExternalSystem) -> Result<keyring::Entry> {
        keyring::Entry::new(KEYRING_SERVICE, &system.to_string())
            .context(format!("Failed to access keyring for {}", system))
    }

    /// Store an API token for the given external system in the OS keyring.
    pub fn store_token(&self, system: &ExternalSystem, token: &str) -> Result<()> {
        let entry = Self::entry(system)?;
        entry
            .set_password(token)
            .context(format!("Failed to store token in keyring for {}", system))
    }

    /// Retrieve the API token for the given external system from the OS keyring.
    pub fn get_token(&self, system: &ExternalSystem) -> Option<String> {
        Self::entry(system)
            .ok()
            .and_then(|e| e.get_password().ok())
    }

    /// Remove the stored token for the given external system from the OS keyring.
    pub fn remove_token(&self, system: &ExternalSystem) -> Result<()> {
        let entry = Self::entry(system)?;
        entry
            .delete_credential()
            .context(format!("Failed to remove token from keyring for {}", system))
    }
}

impl Default for CredentialStore {
    fn default() -> Self {
        Self::new()
    }
}

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

    // Note: These tests interact with the real OS keyring.
    // Each test uses a unique system/key to avoid collisions.

    #[test]
    fn test_store_and_retrieve_token() {
        let store = CredentialStore::new();
        // Store — may fail in CI if no keyring backend is available
        if store.store_token(&ExternalSystem::Linear, "test_lin_tok").is_ok() {
            assert_eq!(
                store.get_token(&ExternalSystem::Linear),
                Some("test_lin_tok".to_string())
            );
            // Clean up
            let _ = store.remove_token(&ExternalSystem::Linear);
        }
    }

    #[test]
    fn test_get_missing_token_returns_none() {
        let store = CredentialStore::new();
        // Ensure no stale entry
        let _ = store.remove_token(&ExternalSystem::Jira);
        assert_eq!(store.get_token(&ExternalSystem::Jira), None);
    }

    #[test]
    fn test_token_ref_key_format() {
        assert_eq!(
            CredentialStore::token_ref_key(&ExternalSystem::Linear),
            "furnace-sync:linear"
        );
        assert_eq!(
            CredentialStore::token_ref_key(&ExternalSystem::Jira),
            "furnace-sync:jira"
        );
        assert_eq!(
            CredentialStore::token_ref_key(&ExternalSystem::Notion),
            "furnace-sync:notion"
        );
    }
}
