package dash

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/gorilla/mux"
	"github.com/seaweedfs/seaweedfs/weed/filer"
	"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
	"github.com/seaweedfs/seaweedfs/weed/s3api"
	"github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine"
	"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
	"github.com/seaweedfs/seaweedfs/weed/s3api/s3lifecycle"
)

// MaxBucketLifecycleRules mirrors AWS S3's limit of 1000 lifecycle rules per bucket.
const MaxBucketLifecycleRules = 1000

// MaxBucketLifecycleRuleIDLength mirrors the S3 API's limit on a lifecycle rule's <ID>.
const MaxBucketLifecycleRuleIDLength = 255

// MaxOwnerNameLength is the maximum allowed length for bucket owner identity names.
// This is a reasonable limit to prevent abuse; AWS IAM user names are limited to 64 chars,
// but we use 256 to allow for more complex identity formats (e.g., email addresses).
const MaxOwnerNameLength = 256

// S3 Bucket management data structures for templates
type S3BucketsData struct {
	Username     string     `json:"username"`
	Buckets      []S3Bucket `json:"buckets"`
	TotalBuckets int        `json:"total_buckets"`
	TotalSize    int64      `json:"total_size"`
	LastUpdated  time.Time  `json:"last_updated"`

	// Pagination
	CurrentPage int `json:"current_page"`
	TotalPages  int `json:"total_pages"`
	PageSize    int `json:"page_size"`

	// Sorting
	SortBy    string `json:"sort_by"`
	SortOrder string `json:"sort_order"`
}

type CreateBucketRequest struct {
	Name                string `json:"name"` // validated manually in CreateBucket
	Region              string `json:"region"`
	QuotaSize           int64  `json:"quota_size"`            // Quota size in bytes
	QuotaUnit           string `json:"quota_unit"`            // Unit: MB, GB, TB
	QuotaEnabled        bool   `json:"quota_enabled"`         // Whether quota is enabled
	VersioningEnabled   bool   `json:"versioning_enabled"`    // Whether versioning is enabled
	ObjectLockEnabled   bool   `json:"object_lock_enabled"`   // Whether object lock is enabled
	ObjectLockMode      string `json:"object_lock_mode"`      // Object lock mode: "GOVERNANCE" or "COMPLIANCE"
	SetDefaultRetention bool   `json:"set_default_retention"` // Whether to set default retention
	ObjectLockDuration  int32  `json:"object_lock_duration"`  // Default retention duration in days
	Owner               string `json:"owner"`                 // Bucket owner identity (for S3 IAM authentication)
}

// S3 Bucket Management Handlers

// ShowS3Buckets displays the Object Store buckets management page
func (s *AdminServer) ShowS3Buckets(w http.ResponseWriter, r *http.Request) {
	username := UsernameFromContext(r.Context())

	data, err := s.GetS3BucketsData(1, 100, "name", "asc")
	if err != nil {
		writeJSONError(w, http.StatusInternalServerError, "Failed to get Object Store buckets: "+err.Error())
		return
	}

	data.Username = username
	writeJSON(w, http.StatusOK, data)
}

// ShowBucketDetails displays detailed information about a specific bucket
func (s *AdminServer) ShowBucketDetails(w http.ResponseWriter, r *http.Request) {
	bucketName := mux.Vars(r)["bucket"]
	if bucketName == "" {
		writeJSONError(w, http.StatusBadRequest, "Bucket name is required")
		return
	}

	details, err := s.GetBucketDetails(bucketName)
	if err != nil {
		writeJSONError(w, http.StatusInternalServerError, "Failed to get bucket details: "+err.Error())
		return
	}

	writeJSON(w, http.StatusOK, details)
}

// ShowBucketLifecycle returns the lifecycle configuration for a specific bucket
func (s *AdminServer) ShowBucketLifecycle(w http.ResponseWriter, r *http.Request) {
	bucketName := mux.Vars(r)["bucket"]
	if bucketName == "" {
		writeJSONError(w, http.StatusBadRequest, "Bucket name is required")
		return
	}

	lifecycle, err := s.GetBucketLifecycle(bucketName)
	if err != nil {
		writeJSONError(w, http.StatusInternalServerError, "Failed to get bucket lifecycle: "+err.Error())
		return
	}

	writeJSON(w, http.StatusOK, lifecycle)
}

// UpdateBucketLifecycle replaces the entire lifecycle configuration for a bucket.
func (s *AdminServer) UpdateBucketLifecycle(w http.ResponseWriter, r *http.Request) {
	if !requireSessionCSRFToken(w, r) {
		return
	}

	bucketName := mux.Vars(r)["bucket"]
	if bucketName == "" {
		writeJSONError(w, http.StatusBadRequest, "Bucket name is required")
		return
	}

	var req struct {
		Rules []BucketLifecycleRule `json:"rules"`
	}
	if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil {
		writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error())
		return
	}

	if err := validateBucketLifecycleRules(req.Rules); err != nil {
		writeJSONError(w, http.StatusBadRequest, err.Error())
		return
	}

	if err := s.SetBucketLifecycle(bucketName, req.Rules); err != nil {
		writeJSONError(w, bucketLifecycleErrorStatus(err), "Failed to update bucket lifecycle: "+err.Error())
		return
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"message": "Bucket lifecycle updated successfully",
		"bucket":  bucketName,
	})
}

// DeleteBucketLifecycle clears the entire lifecycle configuration for a bucket.
func (s *AdminServer) DeleteBucketLifecycle(w http.ResponseWriter, r *http.Request) {
	if !requireSessionCSRFToken(w, r) {
		return
	}

	bucketName := mux.Vars(r)["bucket"]
	if bucketName == "" {
		writeJSONError(w, http.StatusBadRequest, "Bucket name is required")
		return
	}

	if err := s.SetBucketLifecycle(bucketName, nil); err != nil {
		writeJSONError(w, bucketLifecycleErrorStatus(err), "Failed to delete bucket lifecycle: "+err.Error())
		return
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"message": "Bucket lifecycle deleted successfully",
		"bucket":  bucketName,
	})
}

// bucketLifecycleErrorStatus keeps a request for a bucket that does not exist
// out of the 5xx bucket, where a client would retry it.
func bucketLifecycleErrorStatus(err error) int {
	if errors.Is(err, ErrBucketNotFound) {
		return http.StatusNotFound
	}
	return http.StatusInternalServerError
}

// validateBucketLifecycleRules rejects a rule set before any write is
// attempted, so a malformed PUT can't half-apply. Mirrors the constraints
// AWS enforces on PutBucketLifecycleConfiguration plus the local rule cap.
func validateBucketLifecycleRules(rules []BucketLifecycleRule) error {
	if len(rules) > MaxBucketLifecycleRules {
		return fmt.Errorf("a bucket may have at most %d lifecycle rules, got %d", MaxBucketLifecycleRules, len(rules))
	}

	seenIDs := make(map[string]bool, len(rules))
	for i, rule := range rules {
		label := fmt.Sprintf("rule %d", i+1)
		if rule.ID != "" {
			label = fmt.Sprintf("rule %q", rule.ID)
			if len(rule.ID) > MaxBucketLifecycleRuleIDLength {
				return fmt.Errorf("%s: ID must be %d characters or less", label, MaxBucketLifecycleRuleIDLength)
			}
			if seenIDs[rule.ID] {
				return fmt.Errorf("duplicate rule ID %q", rule.ID)
			}
			seenIDs[rule.ID] = true
		}

		switch rule.Status {
		case s3lifecycle.StatusEnabled, s3lifecycle.StatusDisabled:
		default:
			return fmt.Errorf("%s: status must be %q or %q, got %q", label, s3lifecycle.StatusEnabled, s3lifecycle.StatusDisabled, rule.Status)
		}

		if rule.ExpirationDays > 0 && rule.ExpirationDate != "" {
			return fmt.Errorf("%s: expiration_days and expiration_date are mutually exclusive", label)
		}
		if rule.ExpirationDays < 0 {
			return fmt.Errorf("%s: expiration_days must be positive", label)
		}
		if rule.ExpirationDate != "" {
			if _, err := time.Parse(time.DateOnly, rule.ExpirationDate); err != nil {
				return fmt.Errorf("%s: invalid expiration_date %q, expected YYYY-MM-DD", label, rule.ExpirationDate)
			}
		}
		if rule.ExpiredObjectDeleteMarker && (rule.ExpirationDays > 0 || rule.ExpirationDate != "") {
			return fmt.Errorf("%s: expired_object_delete_marker cannot be combined with expiration_days or expiration_date", label)
		}

		if rule.NoncurrentVersionExpirationDays < 0 {
			return fmt.Errorf("%s: noncurrent_version_expiration_days must be positive", label)
		}
		if rule.NewerNoncurrentVersions < 0 {
			return fmt.Errorf("%s: newer_noncurrent_versions must be positive", label)
		}
		if rule.AbortMultipartDays < 0 {
			return fmt.Errorf("%s: abort_multipart_days must be positive", label)
		}

		if rule.SizeGreaterThan < 0 || rule.SizeLessThan < 0 {
			return fmt.Errorf("%s: size_greater_than and size_less_than must be positive", label)
		}
		if rule.SizeGreaterThan > 0 && rule.SizeLessThan > 0 && rule.SizeGreaterThan >= rule.SizeLessThan {
			return fmt.Errorf("%s: size_greater_than must be less than size_less_than", label)
		}

		for k, v := range rule.Tags {
			if strings.TrimSpace(k) == "" {
				return fmt.Errorf("%s: tag keys must not be empty", label)
			}
			if strings.TrimSpace(v) == "" {
				return fmt.Errorf("%s: tag value for key %q must not be empty", label, k)
			}
		}

		hasAction := rule.ExpirationDays > 0 || rule.ExpirationDate != "" || rule.ExpiredObjectDeleteMarker ||
			rule.NoncurrentVersionExpirationDays > 0 || rule.NewerNoncurrentVersions > 0 || rule.AbortMultipartDays > 0
		if !hasAction {
			return fmt.Errorf("%s: must specify at least one action (expiration, noncurrent version expiration, or abort incomplete multipart upload)", label)
		}
	}

	return nil
}

// ShowBucketPolicy returns the policy document for a specific bucket, or
// {"bucket": ..., "policy": null} if the bucket has none.
func (s *AdminServer) ShowBucketPolicy(w http.ResponseWriter, r *http.Request) {
	bucketName := mux.Vars(r)["bucket"]
	if bucketName == "" {
		writeJSONError(w, http.StatusBadRequest, "Bucket name is required")
		return
	}

	policy, raw, err := s.GetBucketPolicy(bucketName)
	if err != nil {
		writeJSONError(w, bucketPolicyErrorStatus(err), "Failed to get bucket policy: "+err.Error())
		return
	}

	resp := map[string]interface{}{
		"bucket": bucketName,
		"policy": policy,
	}
	if policy == nil && len(raw) > 0 {
		// Stored bytes the decoder rejects: hand them to the JSON tab so
		// the operator can fix or delete the document.
		resp["policy_text"] = string(raw)
	}
	writeJSON(w, http.StatusOK, resp)
}

// UpdateBucketPolicy replaces the bucket policy for a bucket.
func (s *AdminServer) UpdateBucketPolicy(w http.ResponseWriter, r *http.Request) {
	if !requireSessionCSRFToken(w, r) {
		return
	}

	bucketName := mux.Vars(r)["bucket"]
	if bucketName == "" {
		writeJSONError(w, http.StatusBadRequest, "Bucket name is required")
		return
	}

	var req struct {
		Policy *policy_engine.PolicyDocument `json:"policy"`
	}
	if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil {
		writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error())
		return
	}
	if req.Policy == nil {
		writeJSONError(w, http.StatusBadRequest, "policy is required; use DELETE to clear a bucket policy")
		return
	}

	if err := s.SetBucketPolicy(bucketName, req.Policy); err != nil {
		writeJSONError(w, bucketPolicyErrorStatus(err), "Failed to update bucket policy: "+err.Error())
		return
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"message": "Bucket policy updated successfully",
		"bucket":  bucketName,
	})
}

// RemoveBucketPolicy clears the bucket policy for a bucket. Named
// "Remove", not "Delete", because (*AdminServer).DeleteBucketPolicy is the
// data-layer method this handler calls.
func (s *AdminServer) RemoveBucketPolicy(w http.ResponseWriter, r *http.Request) {
	if !requireSessionCSRFToken(w, r) {
		return
	}

	bucketName := mux.Vars(r)["bucket"]
	if bucketName == "" {
		writeJSONError(w, http.StatusBadRequest, "Bucket name is required")
		return
	}

	if err := s.DeleteBucketPolicy(bucketName); err != nil {
		writeJSONError(w, bucketPolicyErrorStatus(err), "Failed to delete bucket policy: "+err.Error())
		return
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"message": "Bucket policy deleted successfully",
		"bucket":  bucketName,
	})
}

// bucketPolicyErrorStatus keeps a request for a bucket that does not exist,
// or an invalid policy document, out of the 5xx bucket where a client
// would retry it. Mirrors bucketLifecycleErrorStatus.
func bucketPolicyErrorStatus(err error) int {
	if errors.Is(err, ErrBucketNotFound) {
		return http.StatusNotFound
	}
	if errors.Is(err, ErrInvalidBucketPolicy) {
		return http.StatusBadRequest
	}
	return http.StatusInternalServerError
}

// CreateBucket creates a new S3 bucket
func (s *AdminServer) CreateBucket(w http.ResponseWriter, r *http.Request) {
	var req CreateBucketRequest
	if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil {
		writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error())
		return
	}
	if strings.TrimSpace(req.Name) == "" {
		writeJSONError(w, http.StatusBadRequest, "Bucket name is required")
		return
	}

	// Validate bucket name (basic validation)
	if len(req.Name) < 3 || len(req.Name) > 63 {
		writeJSONError(w, http.StatusBadRequest, "Bucket name must be between 3 and 63 characters")
		return
	}

	// Validate object lock settings
	if req.ObjectLockEnabled {
		// Object lock requires versioning to be enabled
		req.VersioningEnabled = true

		// Validate object lock mode
		if req.ObjectLockMode != "GOVERNANCE" && req.ObjectLockMode != "COMPLIANCE" {
			writeJSONError(w, http.StatusBadRequest, "Object lock mode must be either GOVERNANCE or COMPLIANCE")
			return
		}

		// Validate retention duration if default retention is enabled
		if req.SetDefaultRetention {
			if req.ObjectLockDuration <= 0 {
				writeJSONError(w, http.StatusBadRequest, "Object lock duration must be greater than 0 days when default retention is enabled")
				return
			}
		}
	}

	normalizedUnit, err := normalizeQuotaUnit(req.QuotaUnit)
	if err != nil {
		writeJSONError(w, http.StatusBadRequest, err.Error())
		return
	}
	req.QuotaUnit = normalizedUnit
	quotaBytes := convertQuotaToBytes(req.QuotaSize, normalizedUnit)

	// Validate quota: if enabled, size must be greater than 0
	if req.QuotaEnabled && quotaBytes <= 0 {
		writeJSONError(w, http.StatusBadRequest, "Quota size must be greater than 0 when quota is enabled")
		return
	}

	// Sanitize owner: trim whitespace and enforce max length
	owner := strings.TrimSpace(req.Owner)
	if len(owner) > MaxOwnerNameLength {
		writeJSONError(w, http.StatusBadRequest, fmt.Sprintf("Owner name must be %d characters or less", MaxOwnerNameLength))
		return
	}

	err = s.CreateS3BucketWithObjectLock(req.Name, quotaBytes, req.QuotaEnabled, req.VersioningEnabled, req.ObjectLockEnabled, req.ObjectLockMode, req.SetDefaultRetention, req.ObjectLockDuration, owner)
	if err != nil {
		writeJSONError(w, http.StatusInternalServerError, "Failed to create bucket: "+err.Error())
		return
	}

	writeJSON(w, http.StatusCreated, map[string]interface{}{
		"message":              "Bucket created successfully",
		"bucket":               req.Name,
		"quota_size":           req.QuotaSize,
		"quota_unit":           req.QuotaUnit,
		"quota_enabled":        req.QuotaEnabled,
		"versioning_enabled":   req.VersioningEnabled,
		"object_lock_enabled":  req.ObjectLockEnabled,
		"object_lock_mode":     req.ObjectLockMode,
		"object_lock_duration": req.ObjectLockDuration,
		"owner":                owner,
	})
}

// UpdateBucketQuota updates the quota settings for a bucket
func (s *AdminServer) UpdateBucketQuota(w http.ResponseWriter, r *http.Request) {
	bucketName := mux.Vars(r)["bucket"]
	if bucketName == "" {
		writeJSONError(w, http.StatusBadRequest, "Bucket name is required")
		return
	}

	var req struct {
		QuotaSize    int64  `json:"quota_size"`
		QuotaUnit    string `json:"quota_unit"`
		QuotaEnabled bool   `json:"quota_enabled"`
	}
	if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil {
		writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error())
		return
	}

	if req.QuotaEnabled && req.QuotaSize <= 0 {
		writeJSONError(w, http.StatusBadRequest, "quota_size must be > 0 when quota_enabled is true")
		return
	}

	normalizedUnit, err := normalizeQuotaUnit(req.QuotaUnit)
	if err != nil {
		writeJSONError(w, http.StatusBadRequest, err.Error())
		return
	}
	req.QuotaUnit = normalizedUnit
	// Convert quota to bytes
	quotaBytes := convertQuotaToBytes(req.QuotaSize, normalizedUnit)

	err = s.SetBucketQuota(bucketName, quotaBytes, req.QuotaEnabled)
	if err != nil {
		writeJSONError(w, http.StatusInternalServerError, "Failed to update bucket quota: "+err.Error())
		return
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"message":       "Bucket quota updated successfully",
		"bucket":        bucketName,
		"quota_size":    req.QuotaSize,
		"quota_unit":    req.QuotaUnit,
		"quota_enabled": req.QuotaEnabled,
	})
}

// DeleteBucket deletes an S3 bucket
func (s *AdminServer) DeleteBucket(w http.ResponseWriter, r *http.Request) {
	bucketName := mux.Vars(r)["bucket"]
	if bucketName == "" {
		writeJSONError(w, http.StatusBadRequest, "Bucket name is required")
		return
	}

	err := s.DeleteS3Bucket(bucketName)
	if err != nil {
		writeJSONError(w, http.StatusInternalServerError, "Failed to delete bucket: "+err.Error())
		return
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"message": "Bucket deleted successfully",
		"bucket":  bucketName,
	})
}

// UpdateBucketOwner updates the owner of an S3 bucket
func (s *AdminServer) UpdateBucketOwner(w http.ResponseWriter, r *http.Request) {
	bucketName := mux.Vars(r)["bucket"]
	if bucketName == "" {
		writeJSONError(w, http.StatusBadRequest, "Bucket name is required")
		return
	}

	// Use pointer to detect if owner field was explicitly provided
	var req struct {
		Owner *string `json:"owner"`
	}
	if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil {
		writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error())
		return
	}

	// Require owner field to be explicitly provided
	if req.Owner == nil {
		writeJSONError(w, http.StatusBadRequest, "Owner field is required (use empty string to clear owner)")
		return
	}

	// Trim and validate owner
	owner := strings.TrimSpace(*req.Owner)
	if len(owner) > MaxOwnerNameLength {
		writeJSONError(w, http.StatusBadRequest, fmt.Sprintf("Owner name must be %d characters or less", MaxOwnerNameLength))
		return
	}

	err := s.SetBucketOwner(bucketName, owner)
	if err != nil {
		writeJSONError(w, http.StatusInternalServerError, "Failed to update bucket owner: "+err.Error())
		return
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"message": "Bucket owner updated successfully",
		"bucket":  bucketName,
		"owner":   owner,
	})
}

// SetBucketOwner sets the owner of a bucket
func (s *AdminServer) SetBucketOwner(bucketName string, owner string) error {
	return s.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		// Get the current bucket entry
		lookupResp, err := client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{
			Directory: "/buckets",
			Name:      bucketName,
		})
		if err != nil {
			return fmt.Errorf("lookup bucket %s: %w", bucketName, err)
		}

		bucketEntry := lookupResp.Entry

		// Initialize Extended map if nil
		if bucketEntry.Extended == nil {
			bucketEntry.Extended = make(map[string][]byte)
		}

		// Set or remove the owner. The account id recorded by the S3 API goes with
		// it: the S3 API derives it from the owning identity when it is absent, so
		// leaving the old one behind would keep the previous owner of the objects.
		delete(bucketEntry.Extended, s3_constants.ExtAmzOwnerKey)
		if owner == "" {
			delete(bucketEntry.Extended, s3_constants.AmzIdentityId)
		} else {
			bucketEntry.Extended[s3_constants.AmzIdentityId] = []byte(owner)
		}

		// Update the entry
		_, err = client.UpdateEntry(context.Background(), &filer_pb.UpdateEntryRequest{
			Directory: "/buckets",
			Entry:     bucketEntry,
		})
		if err != nil {
			return fmt.Errorf("failed to update bucket owner: %w", err)
		}

		return nil
	})
}

// ListBucketsAPI returns the list of buckets as JSON
func (s *AdminServer) ListBucketsAPI(w http.ResponseWriter, r *http.Request) {
	buckets, err := s.GetS3Buckets()
	if err != nil {
		writeJSONError(w, http.StatusInternalServerError, "Failed to get buckets: "+err.Error())
		return
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"buckets": buckets,
		"total":   len(buckets),
	})
}

// Helper function to convert quota size and unit to bytes
func convertQuotaToBytes(size int64, unit string) int64 {
	if size <= 0 {
		return 0
	}

	switch unit {
	case "TB":
		return size * 1024 * 1024 * 1024 * 1024
	case "GB":
		return size * 1024 * 1024 * 1024
	case "MB":
		return size * 1024 * 1024
	case "KB":
		return size * 1024
	case "B":
		return size
	default:
		return 0
	}
}

func normalizeQuotaUnit(unit string) (string, error) {
	normalized := strings.ToUpper(strings.TrimSpace(unit))
	if normalized == "" {
		return "MB", nil
	}
	switch normalized {
	case "B", "KB", "MB", "GB", "TB":
		return normalized, nil
	default:
		return "", fmt.Errorf("unsupported quota unit: %s", unit)
	}
}

// SetBucketQuota sets the quota for a bucket
func (s *AdminServer) SetBucketQuota(bucketName string, quotaBytes int64, quotaEnabled bool) error {
	return s.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		// Get the current bucket entry
		lookupResp, err := client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{
			Directory: "/buckets",
			Name:      bucketName,
		})
		if err != nil {
			return fmt.Errorf("bucket not found: %w", err)
		}

		bucketEntry := lookupResp.Entry

		// Determine quota value (negative if disabled)
		var quota int64
		if quotaEnabled && quotaBytes > 0 {
			quota = quotaBytes
		} else if !quotaEnabled && quotaBytes > 0 {
			quota = -quotaBytes
		} else {
			quota = 0
		}

		// Update the quota
		bucketEntry.Quota = quota

		// Update the entry
		_, err = client.UpdateEntry(context.Background(), &filer_pb.UpdateEntryRequest{
			Directory: "/buckets",
			Entry:     bucketEntry,
		})
		if err != nil {
			return fmt.Errorf("failed to update bucket quota: %w", err)
		}

		if quota <= 0 {
			// with no active quota, quota enforcement can no longer clear a
			// read-only flag it turned on, so lift it here
			if _, err := filer.ClearBucketReadOnly(context.Background(), client, "/buckets", bucketName); err != nil {
				return fmt.Errorf("failed to clear bucket read-only flag: %w", err)
			}
		}

		return nil
	})
}

// CreateS3BucketWithQuota creates a new S3 bucket with quota settings
func (s *AdminServer) CreateS3BucketWithQuota(bucketName string, quotaBytes int64, quotaEnabled bool) error {
	return s.CreateS3BucketWithObjectLock(bucketName, quotaBytes, quotaEnabled, false, false, "", false, 0, "")
}

// CreateS3BucketWithObjectLock creates a new S3 bucket with quota, versioning, object lock settings, and owner
func (s *AdminServer) CreateS3BucketWithObjectLock(bucketName string, quotaBytes int64, quotaEnabled, versioningEnabled, objectLockEnabled bool, objectLockMode string, setDefaultRetention bool, objectLockDuration int32, owner string) error {
	return s.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		// First ensure /buckets directory exists
		_, err := client.CreateEntry(context.Background(), &filer_pb.CreateEntryRequest{
			Directory: "/",
			Entry: &filer_pb.Entry{
				Name:        "buckets",
				IsDirectory: true,
				Attributes: &filer_pb.FuseAttributes{
					FileMode: uint32(0755 | os.ModeDir), // Directory mode
					Uid:      uint32(1000),
					Gid:      uint32(1000),
					Crtime:   time.Now().Unix(),
					Mtime:    time.Now().Unix(),
					TtlSec:   0,
				},
			},
		})
		// Ignore error if directory already exists
		if err != nil && !strings.Contains(err.Error(), "already exists") && !strings.Contains(err.Error(), "existing entry") {
			return fmt.Errorf("failed to create /buckets directory: %w", err)
		}

		// Check if bucket already exists
		_, err = client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{
			Directory: "/buckets",
			Name:      bucketName,
		})
		if err == nil {
			return fmt.Errorf("bucket %s already exists", bucketName)
		}

		// Determine quota value (negative if disabled)
		var quota int64
		if quotaEnabled && quotaBytes > 0 {
			quota = quotaBytes
		} else if !quotaEnabled && quotaBytes > 0 {
			quota = -quotaBytes
		} else {
			quota = 0
		}

		// Prepare bucket attributes with versioning and object lock metadata
		attributes := &filer_pb.FuseAttributes{
			FileMode: uint32(0755 | os.ModeDir), // Directory mode
			Uid:      filer_pb.OS_UID,
			Gid:      filer_pb.OS_GID,
			Crtime:   time.Now().Unix(),
			Mtime:    time.Now().Unix(),
			TtlSec:   0,
		}

		// Create extended attributes map for versioning and owner
		extended := make(map[string][]byte)

		// Set bucket owner if specified
		if owner != "" {
			extended[s3_constants.AmzIdentityId] = []byte(owner)
		}

		// Create bucket entry
		bucketEntry := &filer_pb.Entry{
			Name:        bucketName,
			IsDirectory: true,
			Attributes:  attributes,
			Extended:    extended,
			Quota:       quota,
		}

		// Handle versioning using shared utilities
		if err := s3api.StoreVersioningInExtended(bucketEntry, versioningEnabled); err != nil {
			return fmt.Errorf("failed to store versioning configuration: %w", err)
		}

		// Handle Object Lock configuration using shared utilities
		if objectLockEnabled {
			var duration int32 = 0
			var mode string = ""

			if setDefaultRetention {
				// Validate Object Lock parameters only when setting default retention
				if err := s3api.ValidateObjectLockParameters(objectLockEnabled, objectLockMode, objectLockDuration); err != nil {
					return fmt.Errorf("invalid Object Lock parameters: %w", err)
				}
				duration = objectLockDuration
				mode = objectLockMode
			}

			// Create Object Lock configuration using shared utility
			objectLockConfig := s3api.CreateObjectLockConfigurationFromParams(objectLockEnabled, mode, duration)

			// Store Object Lock configuration in extended attributes using shared utility
			if err := s3api.StoreObjectLockConfigurationInExtended(bucketEntry, objectLockConfig); err != nil {
				return fmt.Errorf("failed to store Object Lock configuration: %w", err)
			}
		}

		// Create bucket directory under /buckets
		_, err = client.CreateEntry(context.Background(), &filer_pb.CreateEntryRequest{
			Directory: "/buckets",
			Entry:     bucketEntry,
		})
		if err != nil {
			return fmt.Errorf("failed to create bucket directory: %w", err)
		}

		return nil
	})
}
