// kafka — a privacy-respecting metasearch engine
// Copyright (C) 2026-present metamorphosis-dev
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

package middleware

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

// SecurityHeadersConfig controls which security headers are set.
type SecurityHeadersConfig struct {
	// FrameOptions controls X-Frame-Options. Default: "DENY".
	FrameOptions string
	// HSTSMaxAge controls the max-age for Strict-Transport-Security.
	// Set to 0 to disable HSTS (useful for local dev). Default: 31536000 (1 year).
	HSTSMaxAge int
	// HSTSPreloadDomains adds "includeSubDomains; preload" to HSTS.
	HSTSPreloadDomains bool
	// ReferrerPolicy controls the Referrer-Policy header. Default: "no-referrer".
	ReferrerPolicy string
	// CSP controls Content-Security-Policy. Default: a restrictive policy.
	// Set to "" to disable CSP entirely.
	CSP string
}

// SecurityHeaders returns middleware that sets standard HTTP security headers
// on every response.
func SecurityHeaders(cfg SecurityHeadersConfig) func(http.Handler) http.Handler {
	frameOpts := cfg.FrameOptions
	if frameOpts == "" {
		frameOpts = "DENY"
	}

	hstsAge := cfg.HSTSMaxAge
	if hstsAge == 0 {
		hstsAge = 31536000 // 1 year
	}

	refPol := cfg.ReferrerPolicy
	if refPol == "" {
		refPol = "no-referrer"
	}

	csp := cfg.CSP
	if csp == "" {
		csp = defaultCSP()
	}

	hstsValue := "max-age=" + strconv.Itoa(hstsAge)
	if cfg.HSTSPreloadDomains {
		hstsValue += "; includeSubDomains; preload"
	}

	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("X-Content-Type-Options", "nosniff")
			w.Header().Set("X-Frame-Options", frameOpts)
			w.Header().Set("Referrer-Policy", refPol)
			w.Header().Set("Permissions-Policy", "camera=(), microphone=(), geolocation=()")
			w.Header().Set("Content-Security-Policy", csp)

			if hstsAge > 0 {
				w.Header().Set("Strict-Transport-Security", hstsValue)
			}

			next.ServeHTTP(w, r)
		})
	}
}

// defaultCSP returns a restrictive Content-Security-Policy for the
// metasearch engine.
func defaultCSP() string {
	return strings.Join([]string{
		"default-src 'self'",
		"script-src 'self' 'unsafe-inline' https://unpkg.com",
		"style-src 'self' 'unsafe-inline'",
		"img-src 'self' https: data:",
		"connect-src 'self'",
		"font-src 'self'",
		"frame-ancestors 'none'",
		"base-uri 'self'",
		"form-action 'self'",
	}, "; ")
}
