// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Object Expiration Support for Swift API
//!
//! This module implements automatic object expiration, allowing objects to be
//! automatically deleted after a specified time. This is useful for temporary
//! files, cache data, and time-limited content.
//!
//! # Configuration
//!
//! Object expiration is configured via headers during PUT or POST:
//!
//! - `X-Delete-At`: Unix timestamp when object should be deleted
//! - `X-Delete-After`: Seconds from now when object should be deleted
//!
//! # Usage
//!
//! ```bash
//! # Delete object at specific time (Unix timestamp)
//! swift upload container file.txt -H "X-Delete-At: 1740000000"
//!
//! # Delete object 3600 seconds (1 hour) from now
//! swift upload container file.txt -H "X-Delete-After: 3600"
//!
//! # Update expiration on existing object
//! swift post container file.txt -H "X-Delete-At: 1750000000"
//! ```
//!
//! # Expiration Headers
//!
//! When retrieving objects with expiration set:
//! ```http
//! GET /v1/AUTH_account/container/file.txt
//!
//! HTTP/1.1 200 OK
//! X-Delete-At: 1740000000
//! ```
//!
//! # Cleanup
//!
//! Expired objects are automatically deleted by a background worker that
//! periodically scans for objects past their expiration time.

use super::{SwiftError, SwiftResult};
use std::time::{SystemTime, UNIX_EPOCH};
use tracing::debug;

const LOG_COMPONENT_PROTOCOLS: &str = "protocols";
const LOG_SUBSYSTEM_SWIFT_EXPIRATION: &str = "swift_expiration";
const EVENT_SWIFT_EXPIRATION_STATE: &str = "swift_expiration_state";

/// Parse X-Delete-At header value
///
/// Returns Unix timestamp in seconds
pub fn parse_delete_at(value: &str) -> SwiftResult<u64> {
    value
        .parse::<u64>()
        .map_err(|_| SwiftError::BadRequest(format!("Invalid X-Delete-At value: {}", value)))
}

/// Parse X-Delete-After header value and convert to X-Delete-At
///
/// X-Delete-After is seconds from now, converted to absolute Unix timestamp
pub fn parse_delete_after(value: &str) -> SwiftResult<u64> {
    let seconds = value
        .parse::<u64>()
        .map_err(|_| SwiftError::BadRequest(format!("Invalid X-Delete-After value: {}", value)))?;

    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map_err(|e| SwiftError::InternalServerError(format!("Time error: {}", e)))?
        .as_secs();

    Ok(now + seconds)
}

/// Extract expiration timestamp from request headers
///
/// Checks both X-Delete-At and X-Delete-After headers.
/// X-Delete-After takes precedence and is converted to X-Delete-At.
pub fn extract_expiration(headers: &http::HeaderMap) -> SwiftResult<Option<u64>> {
    // Check X-Delete-After first (takes precedence)
    if let Some(delete_after) = headers.get("x-delete-after")
        && let Ok(value_str) = delete_after.to_str()
    {
        let delete_at = parse_delete_after(value_str)?;
        debug!(event = EVENT_SWIFT_EXPIRATION_STATE, component = LOG_COMPONENT_PROTOCOLS, subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION, state = "delete_after_parsed", delete_after = %value_str, delete_at, "swift expiration state changed");
        return Ok(Some(delete_at));
    }

    // Check X-Delete-At
    if let Some(delete_at) = headers.get("x-delete-at")
        && let Ok(value_str) = delete_at.to_str()
    {
        let timestamp = parse_delete_at(value_str)?;
        debug!(
            event = EVENT_SWIFT_EXPIRATION_STATE,
            component = LOG_COMPONENT_PROTOCOLS,
            subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
            state = "delete_at_parsed",
            delete_at = timestamp,
            "swift expiration state changed"
        );
        return Ok(Some(timestamp));
    }

    Ok(None)
}

/// Check if object has expired
///
/// Returns true if the object's X-Delete-At timestamp is in the past
pub fn is_expired(delete_at: u64) -> bool {
    let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();

    now >= delete_at
}

/// Validate expiration timestamp
///
/// Ensures the timestamp is in the future and not too far in the past
pub fn validate_expiration(delete_at: u64) -> SwiftResult<()> {
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map_err(|e| SwiftError::InternalServerError(format!("Time error: {}", e)))?
        .as_secs();

    // Allow some clock skew (60 seconds in the past)
    if delete_at < now.saturating_sub(60) {
        return Err(SwiftError::BadRequest(format!(
            "X-Delete-At timestamp is too far in the past: {}",
            delete_at
        )));
    }

    // Warn if expiration is more than 10 years in the future
    let ten_years = 10 * 365 * 24 * 60 * 60;
    if delete_at > now + ten_years {
        debug!(
            event = EVENT_SWIFT_EXPIRATION_STATE,
            component = LOG_COMPONENT_PROTOCOLS,
            subsystem = LOG_SUBSYSTEM_SWIFT_EXPIRATION,
            result = "far_future_timestamp",
            delete_at,
            "swift expiration state changed"
        );
    }

    Ok(())
}

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

    #[test]
    fn test_parse_delete_at_valid() {
        let result = parse_delete_at("1740000000");
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), 1740000000);
    }

    #[test]
    fn test_parse_delete_at_invalid() {
        let result = parse_delete_at("not_a_number");
        assert!(result.is_err());

        let result = parse_delete_at("-1");
        assert!(result.is_err());
    }

    #[test]
    fn test_parse_delete_after() {
        let result = parse_delete_after("3600");
        assert!(result.is_ok());

        let delete_at = result.unwrap();
        let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();

        // Should be approximately now + 3600
        assert!(delete_at >= now + 3599 && delete_at <= now + 3601);
    }

    #[test]
    fn test_parse_delete_after_invalid() {
        let result = parse_delete_after("not_a_number");
        assert!(result.is_err());
    }

    #[test]
    fn test_is_expired_past() {
        let past_timestamp = 1000000000; // Year 2001
        assert!(is_expired(past_timestamp));
    }

    #[test]
    fn test_is_expired_future() {
        let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
        let future_timestamp = now + 3600; // 1 hour from now
        assert!(!is_expired(future_timestamp));
    }

    #[test]
    fn test_is_expired_exact() {
        let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
        // At exact timestamp, should be expired (>=)
        assert!(is_expired(now));
    }

    #[test]
    fn test_validate_expiration_future() {
        let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
        let future = now + 3600;

        let result = validate_expiration(future);
        assert!(result.is_ok());
    }

    #[test]
    fn test_validate_expiration_recent_past() {
        let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
        let recent_past = now - 30; // 30 seconds ago (within clock skew)

        let result = validate_expiration(recent_past);
        assert!(result.is_ok());
    }

    #[test]
    fn test_validate_expiration_far_past() {
        let far_past = 1000000000; // Year 2001

        let result = validate_expiration(far_past);
        assert!(result.is_err());
    }

    #[test]
    fn test_validate_expiration_far_future() {
        let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
        let far_future = now + (20 * 365 * 24 * 60 * 60); // 20 years

        // Should still be valid (just logged as warning)
        let result = validate_expiration(far_future);
        assert!(result.is_ok());
    }

    #[test]
    fn test_extract_expiration_delete_at() {
        let mut headers = http::HeaderMap::new();
        headers.insert("x-delete-at", "1740000000".parse().unwrap());

        let result = extract_expiration(&headers);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), Some(1740000000));
    }

    #[test]
    fn test_extract_expiration_delete_after() {
        let mut headers = http::HeaderMap::new();
        headers.insert("x-delete-after", "3600".parse().unwrap());

        let result = extract_expiration(&headers);
        assert!(result.is_ok());

        let delete_at = result.unwrap().unwrap();
        let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();

        assert!(delete_at >= now + 3599 && delete_at <= now + 3601);
    }

    #[test]
    fn test_extract_expiration_delete_after_precedence() {
        let mut headers = http::HeaderMap::new();
        headers.insert("x-delete-at", "1740000000".parse().unwrap());
        headers.insert("x-delete-after", "3600".parse().unwrap());

        let result = extract_expiration(&headers);
        assert!(result.is_ok());

        let delete_at = result.unwrap().unwrap();
        let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();

        // Should use X-Delete-After (precedence), not X-Delete-At
        assert!(delete_at >= now + 3599 && delete_at <= now + 3601);
        assert_ne!(delete_at, 1740000000);
    }

    #[test]
    fn test_extract_expiration_none() {
        let headers = http::HeaderMap::new();

        let result = extract_expiration(&headers);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), None);
    }
}
