//! Object types for object storage

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

/// Unique object identifier (internal)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ObjectId(Uuid);

impl ObjectId {
    /// Create a new random object ID
    pub fn new() -> Self {
        Self(Uuid::new_v4())
    }

    /// Create from existing UUID
    pub fn from_uuid(id: Uuid) -> Self {
        Self(id)
    }

    /// Get the underlying UUID
    pub fn as_uuid(&self) -> &Uuid {
        &self.0
    }
}

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

impl std::fmt::Display for ObjectId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// Object key (path within bucket)
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ObjectKey(String);

impl ObjectKey {
    /// Create a new object key with validation
    pub fn new(key: impl Into<String>) -> Result<Self, &'static str> {
        let key = key.into();

        if key.is_empty() {
            return Err("object key cannot be empty");
        }

        if key.len() > 1024 {
            return Err("object key cannot exceed 1024 characters");
        }

        // Basic S3 key validation - most characters allowed
        // Avoid control characters
        if key.chars().any(|c| c.is_control()) {
            return Err("object key cannot contain control characters");
        }

        Ok(Self(key))
    }

    /// Get the object key as a string slice
    pub fn as_str(&self) -> &str {
        &self.0
    }

    /// Get the "directory" prefix (everything before the last /)
    pub fn prefix(&self) -> Option<&str> {
        self.0.rfind('/').map(|i| &self.0[..i])
    }

    /// Get the "filename" (everything after the last /)
    pub fn filename(&self) -> &str {
        self.0
            .rfind('/')
            .map(|i| &self.0[i + 1..])
            .unwrap_or(&self.0)
    }
}

impl std::fmt::Display for ObjectKey {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl AsRef<str> for ObjectKey {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

/// ETag (entity tag) - typically MD5 hash of object content
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ETag(String);

impl ETag {
    /// Create a new ETag from MD5 hash bytes
    pub fn from_md5(hash: &[u8; 16]) -> Self {
        Self(hex::encode(hash))
    }

    /// Create from hex string
    pub fn from_hex(hex: impl Into<String>) -> Self {
        Self(hex.into())
    }

    /// Create ETag for multipart upload
    pub fn multipart(part_etags: &[ETag], part_count: usize) -> Self {
        // Multipart ETags are formatted as "hash-partcount"
        use md5::{Digest, Md5};
        let mut hasher = Md5::new();
        for etag in part_etags {
            if let Ok(bytes) = hex::decode(&etag.0) {
                hasher.update(&bytes);
            }
        }
        let hash = hasher.finalize();
        Self(format!("{}-{}", hex::encode(hash), part_count))
    }

    /// Get the ETag as a string slice
    pub fn as_str(&self) -> &str {
        &self.0
    }

    /// Check if this is a multipart ETag
    pub fn is_multipart(&self) -> bool {
        self.0.contains('-')
    }
}

impl std::fmt::Display for ETag {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "\"{}\"", self.0)
    }
}

/// Object version identifier
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ObjectVersion(String);

impl ObjectVersion {
    /// Null version (for non-versioned buckets)
    pub const NULL: &'static str = "null";

    /// Create a new version ID
    pub fn new() -> Self {
        Self(Uuid::new_v4().to_string())
    }

    /// Null version
    pub fn null() -> Self {
        Self(Self::NULL.to_string())
    }

    /// Get the version as a string slice
    pub fn as_str(&self) -> &str {
        &self.0
    }

    /// Check if this is the null version
    pub fn is_null(&self) -> bool {
        self.0 == Self::NULL
    }
}

impl Default for ObjectVersion {
    fn default() -> Self {
        Self::null()
    }
}

impl std::fmt::Display for ObjectVersion {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// Object metadata (custom user metadata)
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ObjectMetadata {
    /// Content type (MIME type)
    pub content_type: Option<String>,
    /// Content encoding (e.g., gzip)
    pub content_encoding: Option<String>,
    /// Content disposition
    pub content_disposition: Option<String>,
    /// Content language
    pub content_language: Option<String>,
    /// Cache control header
    pub cache_control: Option<String>,
    /// Custom user metadata (x-amz-meta-*)
    pub user_metadata: std::collections::HashMap<String, String>,
}

/// A stored object
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Object {
    /// Internal object ID
    pub id: ObjectId,
    /// Bucket ID this object belongs to
    pub bucket_id: String,
    /// Object key (path)
    pub key: ObjectKey,
    /// Version ID
    pub version: ObjectVersion,
    /// ETag (content hash)
    pub etag: ETag,
    /// Object size in bytes
    pub size: u64,
    /// Object metadata
    pub metadata: ObjectMetadata,
    /// Storage class (STANDARD, INFREQUENT, ARCHIVE)
    pub storage_class: String,
    /// Is this the latest version?
    pub is_latest: bool,
    /// Is this a delete marker?
    pub is_delete_marker: bool,
    /// Creation timestamp
    pub created_at: DateTime<Utc>,
    /// Last modified timestamp
    pub last_modified: DateTime<Utc>,
    /// Expiration time (if lifecycle rule applies)
    pub expires_at: Option<DateTime<Utc>>,
}

impl Object {
    /// Create a new object
    pub fn new(
        bucket_id: impl Into<String>,
        key: ObjectKey,
        etag: ETag,
        size: u64,
        content_type: Option<String>,
    ) -> Self {
        let now = Utc::now();
        Self {
            id: ObjectId::new(),
            bucket_id: bucket_id.into(),
            key,
            version: ObjectVersion::null(),
            etag,
            size,
            metadata: ObjectMetadata {
                content_type,
                ..Default::default()
            },
            storage_class: "STANDARD".to_string(),
            is_latest: true,
            is_delete_marker: false,
            created_at: now,
            last_modified: now,
            expires_at: None,
        }
    }
}

/// Delete marker (for versioned buckets)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeleteMarker {
    /// Object key
    pub key: ObjectKey,
    /// Version ID of the delete marker
    pub version: ObjectVersion,
    /// Is this the latest version?
    pub is_latest: bool,
    /// Creation timestamp
    pub created_at: DateTime<Utc>,
}

/// Multipart upload ID
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct UploadId(String);

impl UploadId {
    /// Create a new upload ID
    pub fn new() -> Self {
        Self(Uuid::new_v4().to_string())
    }

    /// Get the upload ID as a string slice
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

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

impl std::fmt::Display for UploadId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl std::str::FromStr for UploadId {
    type Err = std::convert::Infallible;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self(s.to_string()))
    }
}

/// Part number (1-10000)
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct PartNumber(u32);

impl PartNumber {
    /// Minimum part number
    pub const MIN: u32 = 1;
    /// Maximum part number
    pub const MAX: u32 = 10000;

    /// Create a new part number with validation
    pub fn new(n: u32) -> Result<Self, &'static str> {
        if !(Self::MIN..=Self::MAX).contains(&n) {
            return Err("part number must be between 1 and 10000");
        }
        Ok(Self(n))
    }

    /// Get the part number as u32
    pub fn as_u32(&self) -> u32 {
        self.0
    }
}

impl std::fmt::Display for PartNumber {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// An uploaded part of a multipart upload
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Part {
    /// Part number
    pub part_number: PartNumber,
    /// ETag of the part
    pub etag: ETag,
    /// Size in bytes
    pub size: u64,
    /// Last modified timestamp
    pub last_modified: DateTime<Utc>,
}

/// Multipart upload state
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MultipartUpload {
    /// Upload ID
    pub upload_id: UploadId,
    /// Bucket ID
    pub bucket_id: String,
    /// Object key
    pub key: ObjectKey,
    /// Initiated timestamp
    pub initiated: DateTime<Utc>,
    /// Object metadata (set at initiation)
    pub metadata: ObjectMetadata,
    /// Uploaded parts
    pub parts: Vec<Part>,
}

impl MultipartUpload {
    /// Create a new multipart upload
    pub fn new(bucket_id: impl Into<String>, key: ObjectKey) -> Self {
        Self {
            upload_id: UploadId::new(),
            bucket_id: bucket_id.into(),
            key,
            initiated: Utc::now(),
            metadata: ObjectMetadata::default(),
            parts: Vec::new(),
        }
    }
}


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

    #[test]
    fn test_object_key_validation() {
        assert!(ObjectKey::new("simple-key").is_ok());
        assert!(ObjectKey::new("path/to/object.txt").is_ok());
        assert!(ObjectKey::new("").is_err()); // empty
    }

    #[test]
    fn test_part_number_validation() {
        assert!(PartNumber::new(1).is_ok());
        assert!(PartNumber::new(10000).is_ok());
        assert!(PartNumber::new(0).is_err());
        assert!(PartNumber::new(10001).is_err());
    }
}
