package auth

import (
	"encoding/hex"
	"errors"
	"net/http"
	"os"

	"github.com/gorilla/csrf"
)

// ErrCSRFKeyInvalid is returned by LoadKeyFromEnv when SESSION_SECRET is
// missing or decodes to a length other than 32 bytes.
var ErrCSRFKeyInvalid = errors.New("SESSION_SECRET must be a 64-char hex string encoding 32 bytes; generate with: openssl rand -hex 32")

// LoadKeyFromEnv reads SESSION_SECRET from the environment, hex-decodes it,
// and validates that the result is exactly 32 bytes. Returns ErrCSRFKeyInvalid
// for a missing, non-hex, or wrong-length value. The caller (cmd/web/main.go)
// should log.Fatalf on error.
func LoadKeyFromEnv() ([]byte, error) {
	raw := os.Getenv("SESSION_SECRET")
	if raw == "" {
		return nil, ErrCSRFKeyInvalid
	}
	key, err := hex.DecodeString(raw)
	if err != nil {
		return nil, ErrCSRFKeyInvalid
	}
	if len(key) != 32 {
		return nil, ErrCSRFKeyInvalid
	}
	return key, nil
}

// Mount returns a gorilla/csrf middleware configured with the locked options
// from CONTEXT D-14 / D-24:
//
//   - csrf.Secure(env != "dev"): sets the Secure flag on the _gorilla_csrf
//     cookie in all environments except "dev" (plain-HTTP local development).
//   - csrf.SameSite(csrf.SameSiteLaxMode): SameSite=Lax interim defense.
//   - csrf.Path("/"): the CSRF cookie is scoped to the entire site.
//   - csrf.FieldName("_csrf"): the hidden form field name (matches @ui.CSRFField).
//   - csrf.RequestHeader("X-CSRF-Token"): accepted header for HTMX hx-headers usage.
//
// The middleware is mounted AFTER auth.ResolveSession and BEFORE any route
// group (D-24, Pitfall 7).
//
// When env == "dev", requests are additionally marked as plaintext HTTP via
// csrf.PlaintextHTTPRequest so gorilla/csrf skips the Referer-based origin
// check (which only applies to TLS). This allows local development and
// integration tests running over plain HTTP to function correctly.
//
// trustedOrigins is an optional list of additional trusted origins (used in
// tests to allow localhost requests without a Referer header).
// In production, pass nil — SameSite=Lax and the CSRF cookie handle the defense.
func Mount(env string, key []byte, trustedOrigins ...string) func(http.Handler) http.Handler {
	isDev := env == "dev" || env == "development"
	opts := []csrf.Option{
		csrf.Secure(!isDev),
		csrf.SameSite(csrf.SameSiteLaxMode),
		csrf.Path("/"),
		csrf.FieldName("_csrf"),
		csrf.RequestHeader("X-CSRF-Token"),
	}
	if len(trustedOrigins) > 0 {
		opts = append(opts, csrf.TrustedOrigins(trustedOrigins))
	}
	csrfMiddleware := csrf.Protect(key, opts...)

	// In dev mode, mark every request as plaintext HTTP so gorilla/csrf skips
	// the Referer-based TLS origin check. This is safe: dev mode already has
	// Secure=false on the cookie, and SameSite=Lax provides interim protection.
	if isDev {
		return func(next http.Handler) http.Handler {
			return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				// Tag the request as plaintext HTTP before csrf.Protect sees it.
				r = csrf.PlaintextHTTPRequest(r)
				csrfMiddleware(next).ServeHTTP(w, r)
			})
		}
	}

	return csrfMiddleware
}
