package main

import (
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

const (
	authCookieName   = "datascape_auth"
	authCookieMaxAge = 10 * 365 * 24 * 3600
)

// loadOrCreateAuthKey returns a stable 32-byte HMAC key persisted in the wiki
// root as `.auth-key`. A stable key means sessions survive restarts.
func loadOrCreateAuthKey(wikiDir string) ([]byte, error) {
	p := filepath.Join(wikiDir, ".auth-key")
	if data, err := os.ReadFile(p); err == nil && len(data) >= 32 {
		return data, nil
	}
	key := make([]byte, 32)
	if _, err := rand.Read(key); err != nil {
		return nil, err
	}
	if err := os.WriteFile(p, key, 0600); err != nil {
		return nil, err
	}
	return key, nil
}

func signAuth(key []byte) string {
	payload := strconv.FormatInt(time.Now().Unix(), 10)
	mac := hmac.New(sha256.New, key)
	mac.Write([]byte(payload))
	sig := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
	return payload + "." + sig
}

func verifyAuth(key []byte, value string) bool {
	i := strings.IndexByte(value, '.')
	if i <= 0 {
		return false
	}
	payload, sig := value[:i], value[i+1:]
	mac := hmac.New(sha256.New, key)
	mac.Write([]byte(payload))
	expected := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
	return hmac.Equal([]byte(sig), []byte(expected))
}

// checkAuth returns true if the request is authenticated. It accepts either a
// valid signed cookie or HTTP Basic credentials; on successful basic auth it
// issues a long-lived cookie so the browser stops re-prompting.
func (h *handler) checkAuth(w http.ResponseWriter, r *http.Request) bool {
	if h.user == "" {
		return true
	}
	if c, err := r.Cookie(authCookieName); err == nil && verifyAuth(h.authKey, c.Value) {
		return true
	}
	u, p, ok := r.BasicAuth()
	if !ok || u != h.user || p != h.pass {
		w.Header().Set("WWW-Authenticate", `Basic realm="datascape"`)
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return false
	}
	http.SetCookie(w, &http.Cookie{
		Name:     authCookieName,
		Value:    signAuth(h.authKey),
		Path:     "/",
		MaxAge:   authCookieMaxAge,
		HttpOnly: true,
		SameSite: http.SameSiteLaxMode,
	})
	return true
}

// handleLogout clears the session cookie and forces a fresh basic-auth prompt.
func (h *handler) handleLogout(w http.ResponseWriter, r *http.Request) {
	http.SetCookie(w, &http.Cookie{
		Name:     authCookieName,
		Value:    "",
		Path:     "/",
		MaxAge:   -1,
		HttpOnly: true,
		SameSite: http.SameSiteLaxMode,
	})
	w.Header().Set("WWW-Authenticate", `Basic realm="datascape"`)
	http.Error(w, "Logged out", http.StatusUnauthorized)
}
