package cors

import (
	"net/http"

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

// BucketChecker interface for checking bucket existence
type BucketChecker interface {
	CheckBucket(r *http.Request, bucket string) s3err.ErrorCode
}

// CORSConfigGetter interface for getting CORS configuration
type CORSConfigGetter interface {
	GetCORSConfiguration(bucket string) (*CORSConfiguration, s3err.ErrorCode)
}

// Middleware handles CORS evaluation for all S3 API requests
type Middleware struct {
	bucketChecker    BucketChecker
	corsConfigGetter CORSConfigGetter
	fallbackConfig   *CORSConfiguration // Global CORS configuration as fallback
}

// NewMiddleware creates a new CORS middleware instance with optional global fallback config
func NewMiddleware(bucketChecker BucketChecker, corsConfigGetter CORSConfigGetter, fallbackConfig *CORSConfiguration) *Middleware {
	return &Middleware{
		bucketChecker:    bucketChecker,
		corsConfigGetter: corsConfigGetter,
		fallbackConfig:   fallbackConfig,
	}
}

// getCORSConfig retrieves the applicable CORS configuration, trying bucket-specific first, then fallback.
// Returns the configuration and a boolean indicating if any configuration was found.
// Only falls back to global config when there's explicitly no bucket-level config.
// For other errors (e.g., access denied), returns false to let the handler deny the request.
func (m *Middleware) getCORSConfig(bucket string) (*CORSConfiguration, bool) {
	config, errCode := m.corsConfigGetter.GetCORSConfiguration(bucket)

	switch errCode {
	case s3err.ErrNone:
		if config != nil {
			// Found a bucket-specific config, use it.
			return config, true
		}
		// No bucket config, proceed to fallback.
	case s3err.ErrNoSuchCORSConfiguration:
		// No bucket config, proceed to fallback.
	case s3err.ErrNoSuchBucket:
		// Bucket doesn't exist, proceed to fallback.
		// This ensures we don't leak existence information by returning 403 vs 200.
	default:
		// Any other error means we should not proceed.
		return nil, false
	}

	// No bucket-level config found, try global fallback
	if m.fallbackConfig != nil {
		return m.fallbackConfig, true
	}

	return nil, false
}

// Handler returns the CORS middleware handler
func (m *Middleware) Handler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		m.processCORS(w, r, func() {
			next.ServeHTTP(w, r)
		})
	})
}

// HandleOptionsRequest handles OPTIONS requests for CORS preflight
func (m *Middleware) HandleOptionsRequest(w http.ResponseWriter, r *http.Request) {
	m.processCORS(w, r, func() {
		w.WriteHeader(http.StatusOK)
	})
}

// processCORS handles the common CORS logic for both regular and preflight requests.
// It takes a next callback which is executed if the request flow proceeds (i.e., not short-circuited by CORS errors or preflight responses).
func (m *Middleware) processCORS(w http.ResponseWriter, r *http.Request, next func()) {
	corsReq := ParseRequest(r)
	bucket, _ := s3_constants.GetBucketAndObject(r)

	// 1. Basic Validation
	if bucket == "" {
		next()
		return
	}

	// 2. Load Configuration
	config, hasConfig := m.getCORSConfig(bucket)

	// 3. Apply Vary Header (Always applied if config exists)
	if hasConfig {
		w.Header().Add("Vary", "Origin")
	}

	// 4. Handle Non-CORS Requests
	if corsReq.Origin == "" {
		next()
		return
	}

	// 5. Handle Missing Configuration
	if !hasConfig {
		if corsReq.IsPreflightRequest {
			s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
			return
		}
		next()
		return
	}

	// 6. Evaluate CORS Request
	corsResp, err := EvaluateRequest(config, corsReq)
	if err != nil {
		glog.V(3).Infof("CORS evaluation failed for bucket %s: %v", bucket, err)
		if corsReq.IsPreflightRequest {
			s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
			return
		}
		next()
		return
	}

	// 7. Success Case
	ApplyHeaders(w, corsResp)

	if corsReq.IsPreflightRequest {
		w.WriteHeader(http.StatusOK)
		return
	}

	next()
}
