package s3api

import (
	"fmt"
	"net/http"

	"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
	"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
)

// CopyValidationError represents validation errors during copy operations
type CopyValidationError struct {
	Code    s3err.ErrorCode
	Message string
}

func (e *CopyValidationError) Error() string {
	return e.Message
}

// ValidateCopyEncryption performs comprehensive validation of copy encryption parameters
func ValidateCopyEncryption(srcMetadata map[string][]byte, headers http.Header) error {
	// Validate SSE-C copy requirements
	if err := validateSSECCopyRequirements(srcMetadata, headers); err != nil {
		return err
	}

	// Validate SSE-KMS copy requirements
	if err := validateSSEKMSCopyRequirements(srcMetadata, headers); err != nil {
		return err
	}

	// Validate incompatible encryption combinations
	if err := validateEncryptionCompatibility(headers); err != nil {
		return err
	}

	return nil
}

// validateSSECCopyRequirements validates SSE-C copy header requirements
func validateSSECCopyRequirements(srcMetadata map[string][]byte, headers http.Header) error {
	srcIsSSEC := IsSSECEncrypted(srcMetadata)
	hasCopyHeaders := hasSSECCopyHeaders(headers)
	hasSSECHeaders := hasSSECHeaders(headers)

	// If source is SSE-C encrypted, copy headers are required
	if srcIsSSEC && !hasCopyHeaders {
		return &CopyValidationError{
			Code:    s3err.ErrInvalidRequest,
			Message: "SSE-C encrypted source requires copy source encryption headers",
		}
	}

	// If copy headers are provided, source must be SSE-C encrypted
	if hasCopyHeaders && !srcIsSSEC {
		return &CopyValidationError{
			Code:    s3err.ErrInvalidRequest,
			Message: "SSE-C copy headers provided but source is not SSE-C encrypted",
		}
	}

	// Validate copy header completeness
	if hasCopyHeaders {
		if err := validateSSECCopyHeaderCompleteness(headers); err != nil {
			return err
		}
	}

	// Validate destination SSE-C headers if present
	if hasSSECHeaders {
		if err := validateSSECHeaderCompleteness(headers); err != nil {
			return err
		}
	}

	return nil
}

// validateSSEKMSCopyRequirements validates SSE-KMS copy requirements
func validateSSEKMSCopyRequirements(srcMetadata map[string][]byte, headers http.Header) error {
	dstIsSSEKMS := IsSSEKMSRequest(&http.Request{Header: headers})

	// Validate KMS key ID format if provided
	if dstIsSSEKMS {
		keyID := headers.Get(s3_constants.AmzServerSideEncryptionAwsKmsKeyId)
		if keyID != "" && !isValidKMSKeyID(keyID) {
			return &CopyValidationError{
				Code:    s3err.ErrKMSKeyNotFound,
				Message: fmt.Sprintf("Invalid KMS key ID format: %s", keyID),
			}
		}
	}

	// Validate encryption context format if provided
	if contextHeader := headers.Get(s3_constants.AmzServerSideEncryptionContext); contextHeader != "" {
		if !dstIsSSEKMS {
			return &CopyValidationError{
				Code:    s3err.ErrInvalidRequest,
				Message: "Encryption context can only be used with SSE-KMS",
			}
		}

		// Validate base64 encoding and JSON format
		if err := validateEncryptionContext(contextHeader); err != nil {
			return &CopyValidationError{
				Code:    s3err.ErrInvalidRequest,
				Message: fmt.Sprintf("Invalid encryption context: %v", err),
			}
		}
	}

	return nil
}

// validateEncryptionCompatibility validates that encryption methods are not conflicting
func validateEncryptionCompatibility(headers http.Header) error {
	sseAlgorithm := headers.Get(s3_constants.AmzServerSideEncryption)
	hasSSEC := hasSSECHeaders(headers)
	hasSSEKMS := sseAlgorithm == s3_constants.SSEAlgorithmKMS
	hasSSES3 := sseAlgorithm == s3_constants.SSEAlgorithmAES256

	// Reject unsupported algorithms so they are never persisted as a bogus
	// destination header advertising encryption that was never applied.
	if sseAlgorithm != "" && !hasSSEKMS && !hasSSES3 {
		return &CopyValidationError{
			Code:    s3err.ErrInvalidEncryptionAlgorithm,
			Message: fmt.Sprintf("Unsupported server-side encryption algorithm: %s", sseAlgorithm),
		}
	}

	// Count how many encryption methods are specified
	encryptionCount := 0
	if hasSSEC {
		encryptionCount++
	}
	if hasSSEKMS {
		encryptionCount++
	}
	if hasSSES3 {
		encryptionCount++
	}

	// Only one encryption method should be specified
	if encryptionCount > 1 {
		return &CopyValidationError{
			Code:    s3err.ErrInvalidRequest,
			Message: "Multiple encryption methods specified - only one is allowed",
		}
	}

	return nil
}

// validateSSECCopyHeaderCompleteness validates that all required SSE-C copy headers are present
func validateSSECCopyHeaderCompleteness(headers http.Header) error {
	algorithm := headers.Get(s3_constants.AmzCopySourceServerSideEncryptionCustomerAlgorithm)
	key := headers.Get(s3_constants.AmzCopySourceServerSideEncryptionCustomerKey)
	keyMD5 := headers.Get(s3_constants.AmzCopySourceServerSideEncryptionCustomerKeyMD5)

	if algorithm == "" {
		return &CopyValidationError{
			Code:    s3err.ErrInvalidRequest,
			Message: "SSE-C copy customer algorithm header is required",
		}
	}

	if key == "" {
		return &CopyValidationError{
			Code:    s3err.ErrInvalidRequest,
			Message: "SSE-C copy customer key header is required",
		}
	}

	if keyMD5 == "" {
		return &CopyValidationError{
			Code:    s3err.ErrInvalidRequest,
			Message: "SSE-C copy customer key MD5 header is required",
		}
	}

	// Validate algorithm
	if algorithm != "AES256" {
		return &CopyValidationError{
			Code:    s3err.ErrInvalidRequest,
			Message: fmt.Sprintf("Unsupported SSE-C algorithm: %s", algorithm),
		}
	}

	return nil
}

// validateSSECHeaderCompleteness validates that all required SSE-C headers are present
func validateSSECHeaderCompleteness(headers http.Header) error {
	algorithm := headers.Get(s3_constants.AmzServerSideEncryptionCustomerAlgorithm)
	key := headers.Get(s3_constants.AmzServerSideEncryptionCustomerKey)
	keyMD5 := headers.Get(s3_constants.AmzServerSideEncryptionCustomerKeyMD5)

	if algorithm == "" {
		return &CopyValidationError{
			Code:    s3err.ErrInvalidRequest,
			Message: "SSE-C customer algorithm header is required",
		}
	}

	if key == "" {
		return &CopyValidationError{
			Code:    s3err.ErrInvalidRequest,
			Message: "SSE-C customer key header is required",
		}
	}

	if keyMD5 == "" {
		return &CopyValidationError{
			Code:    s3err.ErrInvalidRequest,
			Message: "SSE-C customer key MD5 header is required",
		}
	}

	// Validate algorithm
	if algorithm != "AES256" {
		return &CopyValidationError{
			Code:    s3err.ErrInvalidRequest,
			Message: fmt.Sprintf("Unsupported SSE-C algorithm: %s", algorithm),
		}
	}

	return nil
}

// Helper functions for header detection
func hasSSECCopyHeaders(headers http.Header) bool {
	return headers.Get(s3_constants.AmzCopySourceServerSideEncryptionCustomerAlgorithm) != "" ||
		headers.Get(s3_constants.AmzCopySourceServerSideEncryptionCustomerKey) != "" ||
		headers.Get(s3_constants.AmzCopySourceServerSideEncryptionCustomerKeyMD5) != ""
}

func hasSSECHeaders(headers http.Header) bool {
	return headers.Get(s3_constants.AmzServerSideEncryptionCustomerAlgorithm) != "" ||
		headers.Get(s3_constants.AmzServerSideEncryptionCustomerKey) != "" ||
		headers.Get(s3_constants.AmzServerSideEncryptionCustomerKeyMD5) != ""
}

// validateEncryptionContext validates the encryption context header format
func validateEncryptionContext(contextHeader string) error {
	// This would validate base64 encoding and JSON format
	// Implementation would decode base64 and parse JSON
	// For now, just check it's not empty
	if contextHeader == "" {
		return fmt.Errorf("encryption context cannot be empty")
	}
	return nil
}

// ValidateCopySource validates the copy source path.
func ValidateCopySource(copySource string, srcBucket, srcObject string) error {
	return validateCopySource(copySource, srcBucket, srcObject, "")
}

func validateCopySource(copySource string, srcBucket, srcObject, srcVersionId string) error {
	if copySource == "" {
		return &CopyValidationError{
			Code:    s3err.ErrInvalidCopySource,
			Message: "Copy source header is required",
		}
	}

	if srcBucket == "" {
		return &CopyValidationError{
			Code:    s3err.ErrInvalidCopySource,
			Message: "Source bucket cannot be empty",
		}
	}

	if srcObject == "" {
		return &CopyValidationError{
			Code:    s3err.ErrInvalidCopySource,
			Message: "Source object cannot be empty",
		}
	}

	// `.`/`..` segments are collapsed by the filer's path join; reject them as
	// IsValidObjectKey does for the request URL so the source stays in-bucket.
	if !s3_constants.IsValidBucketName(srcBucket) || !s3_constants.IsValidObjectKey(srcObject) {
		return &CopyValidationError{
			Code:    s3err.ErrInvalidCopySource,
			Message: "Copy source contains invalid path segments",
		}
	}
	if !isValidVersionID(srcVersionId) {
		return &CopyValidationError{
			Code:    s3err.ErrInvalidCopySource,
			Message: "Copy source contains an invalid version ID",
		}
	}

	return nil
}

// ValidateCopyDestination validates the copy destination
func ValidateCopyDestination(dstBucket, dstObject string) error {
	if dstBucket == "" {
		return &CopyValidationError{
			Code:    s3err.ErrInvalidRequest,
			Message: "Destination bucket cannot be empty",
		}
	}

	if dstObject == "" {
		return &CopyValidationError{
			Code:    s3err.ErrInvalidRequest,
			Message: "Destination object cannot be empty",
		}
	}

	return nil
}

// MapCopyValidationError maps validation errors to appropriate S3 error codes
func MapCopyValidationError(err error) s3err.ErrorCode {
	if validationErr, ok := err.(*CopyValidationError); ok {
		return validationErr.Code
	}
	return s3err.ErrInvalidRequest
}
