package middleware

import (
	"net/http"
	"strconv"
	"strings"
)

// CORSConfig configures the CORS middleware. AllowedOrigins is an explicit
// allowlist (no "*" with credentials — that combination is rejected by
// browsers); an entry of "*" disables credentials and echoes "*". When the
// request Origin is not in the allowlist no CORS headers are emitted, so the
// browser blocks the cross-origin read (same as having no CORS support).
type CORSConfig struct {
	Enabled          bool
	AllowedOrigins   []string
	AllowedMethods   []string // empty → GET,POST,PUT,PATCH,DELETE,OPTIONS
	AllowedHeaders   []string // empty → Authorization,Content-Type,X-Client-Request-ID
	AllowCredentials bool
	MaxAgeSeconds    int // preflight cache; 0 → 600
}

// CORS returns middleware that handles cross-origin requests against an explicit
// origin allowlist and short-circuits OPTIONS preflight with 204. When
// cfg.Enabled is false it is a transparent pass-through.
func CORS(cfg CORSConfig) func(http.Handler) http.Handler {
	methods := strings.Join(orDefault(cfg.AllowedMethods,
		[]string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}), ", ")
	headers := strings.Join(orDefault(cfg.AllowedHeaders,
		[]string{"Authorization", "Content-Type", "X-Client-Request-ID"}), ", ")
	maxAge := cfg.MaxAgeSeconds
	if maxAge <= 0 {
		maxAge = 600
	}
	allowAll := false
	allow := map[string]struct{}{}
	for _, o := range cfg.AllowedOrigins {
		if o == "*" {
			allowAll = true
		}
		allow[o] = struct{}{}
	}

	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if !cfg.Enabled {
				next.ServeHTTP(w, r)
				return
			}
			origin := r.Header.Get("Origin")
			if origin != "" && originAllowed(origin, allow, allowAll) {
				h := w.Header()
				// Echo the specific origin (not "*") so credentials can flow;
				// vary on Origin so caches don't serve the wrong ACAO.
				if allowAll && !cfg.AllowCredentials {
					h.Set("Access-Control-Allow-Origin", "*")
				} else {
					h.Set("Access-Control-Allow-Origin", origin)
					h.Add("Vary", "Origin")
				}
				if cfg.AllowCredentials {
					h.Set("Access-Control-Allow-Credentials", "true")
				}
				h.Set("Access-Control-Allow-Methods", methods)
				h.Set("Access-Control-Allow-Headers", headers)
				h.Set("Access-Control-Max-Age", strconv.Itoa(maxAge))
			}
			// Preflight: answer and stop.
			if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" {
				w.WriteHeader(http.StatusNoContent)
				return
			}
			next.ServeHTTP(w, r)
		})
	}
}

func originAllowed(origin string, allow map[string]struct{}, allowAll bool) bool {
	if allowAll {
		return true
	}
	_, ok := allow[origin]
	return ok
}

func orDefault(v, def []string) []string {
	if len(v) == 0 {
		return def
	}
	return v
}
