package authz

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"

	"git.sr.ht/~sircmpwn/core-go/crypto"
	"github.com/fernet/fernet-go"
	"github.com/vaughan0/go-ini"
)

// testConf holds the crypto keys shared by every test. api-origin is filled in
// per-test (it points at an ephemeral httptest server).
var testConf ini.File

// TestMain synthesizes an in-memory config with a fresh Fernet network-key and
// an ed25519 webhook seed, then runs crypto.InitCrypto so Encrypt/Decrypt work.
func TestMain(m *testing.M) {
	var fk fernet.Key
	if err := fk.Generate(); err != nil {
		panic("generate fernet key: " + err.Error())
	}
	seed := make([]byte, 32)
	if _, err := rand.Read(seed); err != nil {
		panic("generate webhook seed: " + err.Error())
	}

	testConf = ini.File{
		"sr.ht":         ini.Section{"network-key": fk.Encode()},
		"webhooks":      ini.Section{"private-key": base64.StdEncoding.EncodeToString(seed)},
		"compare.sr.ht": ini.Section{"origin": "http://localhost"},
	}
	crypto.InitCrypto(testConf)

	os.Exit(m.Run())
}

// sealCookie builds a valid unified-login cookie value carrying the given name.
func sealCookie(t *testing.T, name string) string {
	t.Helper()
	payload, err := json.Marshal(map[string]string{"name": name})
	if err != nil {
		t.Fatalf("marshal claims: %v", err)
	}
	return string(crypto.Encrypt(payload))
}

func TestUsernameFromRequest_RoundTrip(t *testing.T) {
	r := httptest.NewRequest(http.MethodGet, "/", nil)
	r.AddCookie(&http.Cookie{Name: CookieName, Value: sealCookie(t, "bigbes")})
	if got := UsernameFromRequest(r); got != "bigbes" {
		t.Fatalf("username = %q, want %q", got, "bigbes")
	}
}

func TestUsernameFromRequest_StripsTilde(t *testing.T) {
	r := httptest.NewRequest(http.MethodGet, "/", nil)
	r.AddCookie(&http.Cookie{Name: CookieName, Value: sealCookie(t, "~bigbes")})
	if got := UsernameFromRequest(r); got != "bigbes" {
		t.Fatalf("username = %q, want %q", got, "bigbes")
	}
}

func TestUsernameFromRequest_GarbageCookie(t *testing.T) {
	r := httptest.NewRequest(http.MethodGet, "/", nil)
	r.AddCookie(&http.Cookie{Name: CookieName, Value: "not-a-valid-fernet-token"})
	if got := UsernameFromRequest(r); got != "" {
		t.Fatalf("username = %q, want empty", got)
	}
}

func TestUsernameFromRequest_MissingCookie(t *testing.T) {
	r := httptest.NewRequest(http.MethodGet, "/", nil)
	if got := UsernameFromRequest(r); got != "" {
		t.Fatalf("username = %q, want empty", got)
	}
}

func TestUsernameFromRequest_NonJSONPayload(t *testing.T) {
	r := httptest.NewRequest(http.MethodGet, "/", nil)
	// A well-formed Fernet token whose plaintext is not JSON.
	r.AddCookie(&http.Cookie{Name: CookieName, Value: string(crypto.Encrypt([]byte("plain text")))})
	if got := UsernameFromRequest(r); got != "" {
		t.Fatalf("username = %q, want empty", got)
	}
}

func TestMiddlewareAndForContext(t *testing.T) {
	var seen string
	h := Middleware()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		seen = ForContext(r.Context())
	}))

	r := httptest.NewRequest(http.MethodGet, "/", nil)
	r.AddCookie(&http.Cookie{Name: CookieName, Value: sealCookie(t, "bigbes")})
	h.ServeHTTP(httptest.NewRecorder(), r)
	if seen != "bigbes" {
		t.Fatalf("ForContext = %q, want %q", seen, "bigbes")
	}

	// Anonymous request: middleware still runs, ForContext yields "".
	seen = "sentinel"
	h.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", nil))
	if seen != "" {
		t.Fatalf("anonymous ForContext = %q, want empty", seen)
	}
}

func TestForContext_NoValue(t *testing.T) {
	if got := ForContext(httptest.NewRequest(http.MethodGet, "/", nil).Context()); got != "" {
		t.Fatalf("ForContext on bare context = %q, want empty", got)
	}
}
