//! API Key storage (P4.2 Authentication).
//!
//! This module implements API key management for securing the StemeDB HTTP API.
//! Keys are hashed with BLAKE3 before storage and support three role levels:
//!
//! - `ReadOnly`: GET endpoints only
//! - `WriteAgent`: Assert, Vote, Supersede operations
//! - `Admin`: Full access including `/v1/admin/*` endpoints
//!
//! # Storage Layout
//!
//! | Key Pattern | Value | Purpose |
//! |-------------|-------|---------|
//! | `APIKEY:{key_hash_hex}` | Serialized ApiKeyRecord | Primary key record |
//! | `APIKEY:RATE:{key_hash_hex}:{window}` | u64 (LE) | Per-key rate limit usage |
//!
//! # Key Format
//!
//! Keys follow the format: `steme_{env}_{random_hex_48}` where:
//! - `env` is `live` or `test`
//! - `random_hex_48` is 24 bytes (48 hex chars) of cryptographic randomness

mod model;
mod store_impl;

pub use model::{ApiKeyRecord, ApiKeyRole, ParseApiKeyRoleError, RateLimitResult};
pub use store_impl::*;

use crate::error::Result;
use async_trait::async_trait;

/// Default API key rate limit per hour (10,000 requests).
pub const DEFAULT_API_KEY_RATE_LIMIT: u64 = 10_000;

/// Specialized storage trait for API key management.
///
/// This trait provides CRUD operations for API keys and rate limit enforcement.
#[async_trait]
pub trait ApiKeyStore: Send + Sync {
    /// Store a new API key record.
    ///
    /// # Arguments
    /// * `key_hash` - BLAKE3 hash of the raw key
    /// * `record` - The API key metadata record
    async fn put_key(&self, key_hash: &[u8; 32], record: &ApiKeyRecord) -> Result<()>;

    /// Get an API key record by its hash.
    ///
    /// # Arguments
    /// * `key_hash` - BLAKE3 hash of the raw key
    ///
    /// # Returns
    /// The key record if found and enabled, None otherwise
    async fn get_key_by_hash(&self, key_hash: &[u8; 32]) -> Result<Option<ApiKeyRecord>>;

    /// Validate an API key and return its record if valid.
    ///
    /// This checks:
    /// 1. Key exists
    /// 2. Key is enabled
    /// 3. Key is not expired
    ///
    /// # Arguments
    /// * `key_hash` - BLAKE3 hash of the raw key
    /// * `now` - Current Unix timestamp
    ///
    /// # Returns
    /// The key record if valid, None if invalid/expired/disabled
    async fn validate_key(&self, key_hash: &[u8; 32], now: u64) -> Result<Option<ApiKeyRecord>>;

    /// List all API keys (for admin UI).
    ///
    /// Returns key metadata without the ability to recover raw keys.
    async fn list_keys(&self) -> Result<Vec<ApiKeyRecord>>;

    /// Delete (revoke) an API key.
    ///
    /// # Arguments
    /// * `key_hash` - BLAKE3 hash of the key to revoke
    async fn delete_key(&self, key_hash: &[u8; 32]) -> Result<()>;

    /// Update the last_used_at timestamp for a key.
    ///
    /// # Arguments
    /// * `key_hash` - BLAKE3 hash of the key
    /// * `timestamp` - Current Unix timestamp
    async fn touch_key(&self, key_hash: &[u8; 32], timestamp: u64) -> Result<()>;

    /// Check and record API key rate limit usage.
    ///
    /// Uses a per-hour sliding window similar to quota enforcement.
    ///
    /// # Arguments
    /// * `key_hash` - BLAKE3 hash of the key
    /// * `limit` - Per-hour request limit (from key record or default)
    /// * `timestamp` - Current Unix timestamp
    ///
    /// # Returns
    /// Ok(true) if request is allowed, Ok(false) if rate limited
    async fn check_rate_limit(
        &self,
        key_hash: &[u8; 32],
        limit: u64,
        timestamp: u64,
    ) -> Result<RateLimitResult>;

    /// Enable or disable a key without deleting it.
    ///
    /// # Arguments
    /// * `key_hash` - BLAKE3 hash of the key
    /// * `enabled` - New enabled state
    async fn set_enabled(&self, key_hash: &[u8; 32], enabled: bool) -> Result<()>;
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::HybridStore;
    use std::sync::Arc;

    fn test_key_hash() -> [u8; 32] {
        [0xAB; 32]
    }

    #[test]
    fn test_api_key_role_ordering() {
        // Admin > WriteAgent > ReadOnly
        assert!(ApiKeyRole::Admin.can_write());
        assert!(ApiKeyRole::Admin.can_admin());
        assert!(ApiKeyRole::WriteAgent.can_write());
        assert!(!ApiKeyRole::WriteAgent.can_admin());
        assert!(!ApiKeyRole::ReadOnly.can_write());
        assert!(!ApiKeyRole::ReadOnly.can_admin());
    }

    #[test]
    fn test_api_key_record_expiry() {
        let mut record = ApiKeyRecord::new(
            test_key_hash(),
            "steme_test_".to_string(),
            ApiKeyRole::Admin,
            "test_key".to_string(),
            1000,
        );

        // No expiry set
        assert!(!record.is_expired(2000));

        // Set expiry in the future
        record.expires_at = Some(3000);
        assert!(!record.is_expired(2000));

        // Expiry passed
        assert!(record.is_expired(3001));
    }

    #[tokio::test]
    async fn test_put_and_get_key() {
        let store = Arc::new(HybridStore::open_temp().expect("store"));
        let api_key_store = GenericApiKeyStore::new(store);

        let key_hash = test_key_hash();
        let record = ApiKeyRecord::new(
            key_hash,
            "steme_test_".to_string(),
            ApiKeyRole::Admin,
            "root_key".to_string(),
            1000,
        );

        api_key_store.put_key(&key_hash, &record).await.expect("put");

        let retrieved = api_key_store.get_key_by_hash(&key_hash).await.expect("get");
        assert!(retrieved.is_some());

        let retrieved = retrieved.expect("should exist");
        assert_eq!(retrieved.label, "root_key");
        assert_eq!(retrieved.role, ApiKeyRole::Admin);
    }

    #[tokio::test]
    async fn test_validate_key_checks_expiry() {
        let store = Arc::new(HybridStore::open_temp().expect("store"));
        let api_key_store = GenericApiKeyStore::new(store);

        let key_hash = test_key_hash();
        let mut record = ApiKeyRecord::new(
            key_hash,
            "steme_test_".to_string(),
            ApiKeyRole::Admin,
            "expiring_key".to_string(),
            1000,
        );
        record.expires_at = Some(2000);

        api_key_store.put_key(&key_hash, &record).await.expect("put");

        // Valid before expiry
        let result = api_key_store.validate_key(&key_hash, 1500).await.expect("validate");
        assert!(result.is_some());

        // Invalid after expiry
        let result = api_key_store.validate_key(&key_hash, 2500).await.expect("validate");
        assert!(result.is_none());
    }

    #[tokio::test]
    async fn test_validate_key_checks_enabled() {
        let store = Arc::new(HybridStore::open_temp().expect("store"));
        let api_key_store = GenericApiKeyStore::new(store);

        let key_hash = test_key_hash();
        let mut record = ApiKeyRecord::new(
            key_hash,
            "steme_test_".to_string(),
            ApiKeyRole::Admin,
            "disabled_key".to_string(),
            1000,
        );
        record.enabled = false;

        api_key_store.put_key(&key_hash, &record).await.expect("put");

        let result = api_key_store.validate_key(&key_hash, 1500).await.expect("validate");
        assert!(result.is_none());
    }

    #[tokio::test]
    async fn test_rate_limiting() {
        let store = Arc::new(HybridStore::open_temp().expect("store"));
        let api_key_store = GenericApiKeyStore::new(store);

        let key_hash = test_key_hash();
        let limit = 5; // Very low limit for testing
        let timestamp = 1705315800;

        // First 5 requests should succeed
        for i in 0..5 {
            let result =
                api_key_store.check_rate_limit(&key_hash, limit, timestamp).await.expect("check");
            assert!(result.allowed, "Request {} should be allowed", i);
        }

        // 6th request should be rate limited
        let result =
            api_key_store.check_rate_limit(&key_hash, limit, timestamp).await.expect("check");
        assert!(!result.allowed, "Request 6 should be rate limited");
        assert!(result.remaining == 0);
    }

    #[tokio::test]
    async fn test_rate_limit_resets_each_hour() {
        let store = Arc::new(HybridStore::open_temp().expect("store"));
        let api_key_store = GenericApiKeyStore::new(store);

        let key_hash = test_key_hash();
        let limit = 2;
        let hour1 = 1705314000; // 10:00:00
        let hour2 = 1705317600; // 11:00:00

        // Exhaust limit in hour 1
        for _ in 0..2 {
            api_key_store.check_rate_limit(&key_hash, limit, hour1).await.expect("check");
        }

        // Should be rate limited in hour 1
        let result = api_key_store.check_rate_limit(&key_hash, limit, hour1).await.expect("check");
        assert!(!result.allowed);

        // Should succeed in hour 2 (new window)
        let result = api_key_store.check_rate_limit(&key_hash, limit, hour2).await.expect("check");
        assert!(result.allowed);
    }

    #[tokio::test]
    async fn test_list_keys() {
        let store = Arc::new(HybridStore::open_temp().expect("store"));
        let api_key_store = GenericApiKeyStore::new(store);

        // Add two keys
        let key1 = [0x01; 32];
        let key2 = [0x02; 32];

        api_key_store
            .put_key(
                &key1,
                &ApiKeyRecord::new(
                    key1,
                    "steme_test_1".to_string(),
                    ApiKeyRole::Admin,
                    "key1".to_string(),
                    1000,
                ),
            )
            .await
            .expect("put");

        api_key_store
            .put_key(
                &key2,
                &ApiKeyRecord::new(
                    key2,
                    "steme_test_2".to_string(),
                    ApiKeyRole::ReadOnly,
                    "key2".to_string(),
                    1000,
                ),
            )
            .await
            .expect("put");

        let keys = api_key_store.list_keys().await.expect("list");
        assert_eq!(keys.len(), 2);
    }

    #[tokio::test]
    async fn test_delete_key() {
        let store = Arc::new(HybridStore::open_temp().expect("store"));
        let api_key_store = GenericApiKeyStore::new(store);

        let key_hash = test_key_hash();
        let record = ApiKeyRecord::new(
            key_hash,
            "steme_test_".to_string(),
            ApiKeyRole::Admin,
            "to_delete".to_string(),
            1000,
        );

        api_key_store.put_key(&key_hash, &record).await.expect("put");

        // Key exists
        let retrieved = api_key_store.get_key_by_hash(&key_hash).await.expect("get");
        assert!(retrieved.is_some());

        // Delete
        api_key_store.delete_key(&key_hash).await.expect("delete");

        // Key no longer exists
        let retrieved = api_key_store.get_key_by_hash(&key_hash).await.expect("get");
        assert!(retrieved.is_none());
    }

    #[tokio::test]
    async fn test_touch_key() {
        let store = Arc::new(HybridStore::open_temp().expect("store"));
        let api_key_store = GenericApiKeyStore::new(store);

        let key_hash = test_key_hash();
        let record = ApiKeyRecord::new(
            key_hash,
            "steme_test_".to_string(),
            ApiKeyRole::Admin,
            "touchable".to_string(),
            1000,
        );

        api_key_store.put_key(&key_hash, &record).await.expect("put");

        // Touch the key
        api_key_store.touch_key(&key_hash, 2000).await.expect("touch");

        // Verify last_used_at was updated
        let retrieved =
            api_key_store.get_key_by_hash(&key_hash).await.expect("get").expect("exists");
        assert_eq!(retrieved.last_used_at, 2000);
    }
}
