//! Handlers for API key management (P4.2 Authentication).

use axum::{
    extract::{Path, State},
    http::StatusCode,
    Json,
};
use tracing::{info, instrument};

use crate::{
    dto::{
        CreateApiKeyRequest, CreateApiKeyResponse, ErrorResponse, ListApiKeysResponse,
        RevokeApiKeyResponse, RotateApiKeyResponse, UpdateApiKeyRequest, UpdateApiKeyResponse,
    },
    error::{ApiError, Result},
    state::AppState,
};

use stemedb_storage::{ApiKeyRecord, ApiKeyRole, ApiKeyStore, DEFAULT_API_KEY_RATE_LIMIT};

/// Generate a new API key with the given environment prefix.
///
/// Format: `steme_{env}_{random_hex_48}` where random_hex_48 is 24 bytes of entropy.
fn generate_api_key(environment: &str) -> std::result::Result<String, ApiError> {
    let prefix = if environment == "test" { "test" } else { "live" };
    let mut random_bytes = [0u8; 24];
    getrandom::getrandom(&mut random_bytes)
        .map_err(|e| ApiError::Internal(format!("RNG failed: {}", e)))?;
    Ok(format!("steme_{}_{}", prefix, hex::encode(random_bytes)))
}

/// Hash an API key using BLAKE3.
fn hash_api_key(raw_key: &str) -> [u8; 32] {
    *blake3::hash(raw_key.as_bytes()).as_bytes()
}

/// Create a new API key.
///
/// # Request
///
/// ```json
/// {
///   "environment": "live",  // or "test"
///   "role": "admin",        // "read_only", "write_agent", or "admin"
///   "label": "production_api",
///   "rate_limit": 10000,    // optional, requests per hour
///   "expires_at": 1735689600  // optional, Unix timestamp
/// }
/// ```
///
/// # Response
///
/// Returns the raw API key. **Store this securely - it cannot be retrieved later.**
#[utoipa::path(
    post,
    path = "/v1/admin/api-keys",
    request_body = CreateApiKeyRequest,
    responses(
        (status = 201, description = "API key created successfully", body = CreateApiKeyResponse),
        (status = 400, description = "Invalid request", body = ErrorResponse),
        (status = 401, description = "Unauthorized", body = ErrorResponse),
        (status = 403, description = "Forbidden", body = ErrorResponse),
        (status = 500, description = "Internal server error", body = ErrorResponse)
    ),
    tag = "admin"
)]
#[instrument(skip(state, req), fields(label = %req.label, role = %req.role, environment = %req.environment))]
pub async fn create_api_key(
    State(state): State<AppState>,
    Json(req): Json<CreateApiKeyRequest>,
) -> Result<(StatusCode, Json<CreateApiKeyResponse>)> {
    // Validate environment
    if req.environment != "live" && req.environment != "test" {
        return Err(ApiError::InvalidRequest("environment must be 'live' or 'test'".to_string()));
    }

    // Parse role
    let role: ApiKeyRole =
        req.role.parse().map_err(|e: stemedb_storage::ParseApiKeyRoleError| {
            ApiError::InvalidRequest(e.to_string())
        })?;

    // Validate label
    if req.label.is_empty() {
        return Err(ApiError::InvalidRequest("label cannot be empty".to_string()));
    }
    if req.label.len() > 100 {
        return Err(ApiError::InvalidRequest("label cannot exceed 100 characters".to_string()));
    }

    // Generate the key
    let raw_key = generate_api_key(&req.environment)?;
    let key_hash = hash_api_key(&raw_key);
    let key_prefix: String = raw_key.chars().take(12).collect();

    // Get current timestamp
    let now = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_secs())
        .unwrap_or(0);

    // Create record
    let mut record = ApiKeyRecord::new(key_hash, key_prefix.clone(), role, req.label.clone(), now);
    record.rate_limit = req.rate_limit;
    record.expires_at = req.expires_at;

    // Store in database
    state.api_key_store.put_key(&key_hash, &record).await?;

    info!(
        label = %req.label,
        role = %role,
        key_prefix = %key_prefix,
        "Created API key"
    );

    let rate_limit = record.rate_limit.unwrap_or(DEFAULT_API_KEY_RATE_LIMIT);

    Ok((
        StatusCode::CREATED,
        Json(CreateApiKeyResponse {
            key: raw_key,
            key_prefix,
            key_hash: hex::encode(key_hash),
            role: role.as_str().to_string(),
            label: req.label,
            rate_limit,
            expires_at: req.expires_at,
        }),
    ))
}

/// List all API keys.
///
/// Returns metadata about all keys but not the raw keys themselves.
#[utoipa::path(
    get,
    path = "/v1/admin/api-keys",
    responses(
        (status = 200, description = "List of API keys", body = ListApiKeysResponse),
        (status = 401, description = "Unauthorized", body = ErrorResponse),
        (status = 403, description = "Forbidden", body = ErrorResponse),
        (status = 500, description = "Internal server error", body = ErrorResponse)
    ),
    tag = "admin"
)]
#[instrument(skip(state))]
pub async fn list_api_keys(State(state): State<AppState>) -> Result<Json<ListApiKeysResponse>> {
    let records = state.api_key_store.list_keys().await?;
    let total = records.len();

    // Filter out rate limit entries (they have ":RATE:" in their key path)
    // and convert to DTOs
    let keys = records.into_iter().map(|r| r.into()).collect();

    Ok(Json(ListApiKeysResponse { keys, total }))
}

/// Revoke (delete) an API key.
///
/// Once revoked, the key can no longer be used for authentication.
#[utoipa::path(
    delete,
    path = "/v1/admin/api-keys/{key_hash}",
    params(
        ("key_hash" = String, Path, description = "Hex-encoded BLAKE3 hash of the key to revoke")
    ),
    responses(
        (status = 200, description = "API key revoked", body = RevokeApiKeyResponse),
        (status = 400, description = "Invalid key hash", body = ErrorResponse),
        (status = 401, description = "Unauthorized", body = ErrorResponse),
        (status = 403, description = "Forbidden", body = ErrorResponse),
        (status = 404, description = "Key not found", body = ErrorResponse),
        (status = 500, description = "Internal server error", body = ErrorResponse)
    ),
    tag = "admin"
)]
#[instrument(skip(state), fields(key_hash = %key_hash_hex))]
pub async fn revoke_api_key(
    State(state): State<AppState>,
    Path(key_hash_hex): Path<String>,
) -> Result<Json<RevokeApiKeyResponse>> {
    // Parse key hash
    let key_hash_bytes = hex::decode(&key_hash_hex)
        .map_err(|e| ApiError::InvalidHex(format!("Invalid key hash: {}", e)))?;

    if key_hash_bytes.len() != 32 {
        return Err(ApiError::InvalidHashLength { expected: 32, actual: key_hash_bytes.len() });
    }

    let mut key_hash = [0u8; 32];
    key_hash.copy_from_slice(&key_hash_bytes);

    // Check if key exists
    let record = state.api_key_store.get_key_by_hash(&key_hash).await?;
    if record.is_none() {
        return Err(ApiError::NotFound(format!("API key not found: {}", key_hash_hex)));
    }

    // Delete the key
    state.api_key_store.delete_key(&key_hash).await?;

    info!(key_hash = %key_hash_hex, "Revoked API key");

    Ok(Json(RevokeApiKeyResponse { revoked: true, key_hash: key_hash_hex }))
}

/// Rotate an API key (revoke old, create new with same settings).
///
/// Creates a new key with the same role, label, and rate limit as the old key,
/// then revokes the old key.
#[utoipa::path(
    post,
    path = "/v1/admin/api-keys/{key_hash}/rotate",
    params(
        ("key_hash" = String, Path, description = "Hex-encoded BLAKE3 hash of the key to rotate")
    ),
    responses(
        (status = 200, description = "API key rotated", body = RotateApiKeyResponse),
        (status = 400, description = "Invalid key hash", body = ErrorResponse),
        (status = 401, description = "Unauthorized", body = ErrorResponse),
        (status = 403, description = "Forbidden", body = ErrorResponse),
        (status = 404, description = "Key not found", body = ErrorResponse),
        (status = 500, description = "Internal server error", body = ErrorResponse)
    ),
    tag = "admin"
)]
#[instrument(skip(state), fields(key_hash = %key_hash_hex))]
pub async fn rotate_api_key(
    State(state): State<AppState>,
    Path(key_hash_hex): Path<String>,
) -> Result<Json<RotateApiKeyResponse>> {
    // Parse key hash
    let key_hash_bytes = hex::decode(&key_hash_hex)
        .map_err(|e| ApiError::InvalidHex(format!("Invalid key hash: {}", e)))?;

    if key_hash_bytes.len() != 32 {
        return Err(ApiError::InvalidHashLength { expected: 32, actual: key_hash_bytes.len() });
    }

    let mut old_key_hash = [0u8; 32];
    old_key_hash.copy_from_slice(&key_hash_bytes);

    // Get existing key
    let old_record = state
        .api_key_store
        .get_key_by_hash(&old_key_hash)
        .await?
        .ok_or_else(|| ApiError::NotFound(format!("API key not found: {}", key_hash_hex)))?;

    // Determine environment from prefix
    let environment = if old_record.key_prefix.contains("test") { "test" } else { "live" };

    // Generate new key
    let new_raw_key = generate_api_key(environment)?;
    let new_key_hash = hash_api_key(&new_raw_key);
    let new_key_prefix: String = new_raw_key.chars().take(12).collect();

    // Get current timestamp
    let now = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_secs())
        .unwrap_or(0);

    // Create new record with same settings
    let mut new_record = ApiKeyRecord::new(
        new_key_hash,
        new_key_prefix.clone(),
        old_record.role,
        old_record.label.clone(),
        now,
    );
    new_record.rate_limit = old_record.rate_limit;
    // Don't copy expires_at - new key gets fresh settings

    // Store new key
    state.api_key_store.put_key(&new_key_hash, &new_record).await?;

    // Delete old key
    state.api_key_store.delete_key(&old_key_hash).await?;

    info!(
        old_key_hash = %key_hash_hex,
        new_key_prefix = %new_key_prefix,
        label = %old_record.label,
        "Rotated API key"
    );

    Ok(Json(RotateApiKeyResponse {
        new_key: new_raw_key,
        new_key_prefix,
        new_key_hash: hex::encode(new_key_hash),
        old_key_hash: key_hash_hex,
        role: old_record.role.as_str().to_string(),
        label: old_record.label,
    }))
}

/// Update an API key's enabled status.
///
/// Allows disabling a key without deleting it.
#[utoipa::path(
    patch,
    path = "/v1/admin/api-keys/{key_hash}",
    params(
        ("key_hash" = String, Path, description = "Hex-encoded BLAKE3 hash of the key to update")
    ),
    request_body = UpdateApiKeyRequest,
    responses(
        (status = 200, description = "API key updated", body = UpdateApiKeyResponse),
        (status = 400, description = "Invalid key hash", body = ErrorResponse),
        (status = 401, description = "Unauthorized", body = ErrorResponse),
        (status = 403, description = "Forbidden", body = ErrorResponse),
        (status = 404, description = "Key not found", body = ErrorResponse),
        (status = 500, description = "Internal server error", body = ErrorResponse)
    ),
    tag = "admin"
)]
#[instrument(skip(state, req), fields(key_hash = %key_hash_hex, enabled = %req.enabled))]
pub async fn update_api_key(
    State(state): State<AppState>,
    Path(key_hash_hex): Path<String>,
    Json(req): Json<UpdateApiKeyRequest>,
) -> Result<Json<UpdateApiKeyResponse>> {
    // Parse key hash
    let key_hash_bytes = hex::decode(&key_hash_hex)
        .map_err(|e| ApiError::InvalidHex(format!("Invalid key hash: {}", e)))?;

    if key_hash_bytes.len() != 32 {
        return Err(ApiError::InvalidHashLength { expected: 32, actual: key_hash_bytes.len() });
    }

    let mut key_hash = [0u8; 32];
    key_hash.copy_from_slice(&key_hash_bytes);

    // Check if key exists
    let record = state.api_key_store.get_key_by_hash(&key_hash).await?;
    if record.is_none() {
        return Err(ApiError::NotFound(format!("API key not found: {}", key_hash_hex)));
    }

    // Update enabled state
    state.api_key_store.set_enabled(&key_hash, req.enabled).await?;

    let action = if req.enabled { "enabled" } else { "disabled" };
    info!(key_hash = %key_hash_hex, "{} API key", action);

    Ok(Json(UpdateApiKeyResponse { updated: true, key_hash: key_hash_hex, enabled: req.enabled }))
}

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

    #[test]
    fn test_generate_api_key_format() {
        let key = generate_api_key("live").expect("key generation");
        assert!(key.starts_with("steme_live_"));
        assert_eq!(key.len(), 11 + 48); // "steme_live_" + 48 hex chars

        let test_key = generate_api_key("test").expect("key generation");
        assert!(test_key.starts_with("steme_test_"));
    }

    #[test]
    fn test_generate_api_key_uniqueness() {
        let key1 = generate_api_key("live").expect("key1");
        let key2 = generate_api_key("live").expect("key2");
        assert_ne!(key1, key2);
    }

    #[test]
    fn test_hash_api_key_deterministic() {
        let key = "steme_test_abcdef123456";
        let hash1 = hash_api_key(key);
        let hash2 = hash_api_key(key);
        assert_eq!(hash1, hash2);
    }

    #[test]
    fn test_hash_api_key_different_keys() {
        let hash1 = hash_api_key("steme_test_abc");
        let hash2 = hash_api_key("steme_test_def");
        assert_ne!(hash1, hash2);
    }
}
