//! Bucket types for object storage

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

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

impl BucketId {
    /// Create a new random bucket 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 BucketId {
    fn default() -> Self {
        Self::new()
    }
}

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

impl std::str::FromStr for BucketId {
    type Err = uuid::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self(Uuid::parse_str(s)?))
    }
}

/// Validated bucket name (S3-compatible naming rules)
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct BucketName(String);

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

        // S3 bucket naming rules
        if name.len() < 3 || name.len() > 63 {
            return Err("bucket name must be between 3 and 63 characters");
        }

        if !name
            .chars()
            .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-' || c == '.')
        {
            return Err("bucket name can only contain lowercase letters, numbers, hyphens, and periods");
        }

        if !name.chars().next().unwrap().is_ascii_alphanumeric() {
            return Err("bucket name must start with a letter or number");
        }

        if !name.chars().last().unwrap().is_ascii_alphanumeric() {
            return Err("bucket name must end with a letter or number");
        }

        // Cannot look like IP address
        if name.split('.').count() == 4
            && name
                .split('.')
                .all(|part| part.parse::<u8>().is_ok())
        {
            return Err("bucket name cannot be formatted as an IP address");
        }

        Ok(Self(name))
    }

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

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

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

/// Bucket versioning configuration
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Versioning {
    /// Versioning is not enabled (default)
    #[default]
    Disabled,
    /// Versioning is enabled
    Enabled,
    /// Versioning was enabled but is now suspended
    Suspended,
}

/// Bucket status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum BucketStatus {
    /// Bucket is active and accessible
    #[default]
    Active,
    /// Bucket is being created
    Creating,
    /// Bucket is being deleted
    Deleting,
}

/// Bucket access policy
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Default)]
pub struct BucketPolicy {
    /// Policy JSON document (S3 policy format)
    pub policy_json: Option<String>,
    /// Public read access
    pub public_read: bool,
    /// Public write access
    pub public_write: bool,
}


/// A storage bucket containing objects
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bucket {
    /// Unique bucket identifier
    pub id: BucketId,
    /// Bucket name (globally unique within tenant)
    pub name: BucketName,
    /// Organization ID (tenant)
    pub org_id: String,
    /// Project ID (scope)
    pub project_id: String,
    /// Bucket status
    pub status: BucketStatus,
    /// Versioning configuration
    pub versioning: Versioning,
    /// Access policy
    pub policy: BucketPolicy,
    /// Region/location
    pub region: String,
    /// Creation timestamp
    pub created_at: DateTime<Utc>,
    /// Last modified timestamp
    pub updated_at: DateTime<Utc>,
    /// Total size in bytes (approximate)
    pub size_bytes: u64,
    /// Object count (approximate)
    pub object_count: u64,
    /// Custom metadata tags
    pub tags: std::collections::HashMap<String, String>,
}

impl Bucket {
    /// Create a new bucket
    pub fn new(
        name: BucketName,
        org_id: impl Into<String>,
        project_id: impl Into<String>,
        region: impl Into<String>,
    ) -> Self {
        let now = Utc::now();
        Self {
            id: BucketId::new(),
            name,
            org_id: org_id.into(),
            project_id: project_id.into(),
            status: BucketStatus::Active,
            versioning: Versioning::Disabled,
            policy: BucketPolicy::default(),
            region: region.into(),
            created_at: now,
            updated_at: now,
            size_bytes: 0,
            object_count: 0,
            tags: std::collections::HashMap::new(),
        }
    }
}

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

    #[test]
    fn test_bucket_name_validation() {
        assert!(BucketName::new("valid-bucket-name").is_ok());
        assert!(BucketName::new("bucket.with.dots").is_ok());
        assert!(BucketName::new("ab").is_err()); // too short
        assert!(BucketName::new("-invalid").is_err()); // starts with hyphen
        assert!(BucketName::new("192.168.1.1").is_err()); // looks like IP
    }

    #[test]
    fn test_bucket_id() {
        let id = BucketId::new();
        assert!(!id.to_string().is_empty());
    }
}
