package auth

import (
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/golang-jwt/jwt/v5"
)

// testSecret mirrors the format of a Supabase JWT signing key.
var testSecret = []byte("test-secret-for-hs256-verification-123")

func sign(t *testing.T, secret []byte, claims jwt.MapClaims) string {
	t.Helper()
	tok := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	s, err := tok.SignedString(secret)
	if err != nil {
		t.Fatalf("sign: %v", err)
	}
	return s
}

func TestVerifyToken_Valid(t *testing.T) {
	c := &Client{JWTSecret: testSecret}
	token := sign(t, testSecret, jwt.MapClaims{
		"sub": "11111111-1111-1111-1111-111111111111",
		"exp": time.Now().Add(time.Hour).Unix(),
	})
	got, err := c.VerifyToken(token)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if got.Sub != "11111111-1111-1111-1111-111111111111" {
		t.Errorf("sub: got %q", got.Sub)
	}
}

func TestVerifyToken_WrongSecret(t *testing.T) {
	c := &Client{JWTSecret: testSecret}
	token := sign(t, []byte("attacker-guessed-wrong"), jwt.MapClaims{
		"sub": "11111111-1111-1111-1111-111111111111",
		"exp": time.Now().Add(time.Hour).Unix(),
	})
	if _, err := c.VerifyToken(token); err == nil {
		t.Fatal("expected error for wrong-signature token, got nil (auth bypass)")
	}
}

func TestVerifyToken_Expired(t *testing.T) {
	c := &Client{JWTSecret: testSecret}
	token := sign(t, testSecret, jwt.MapClaims{
		"sub": "11111111-1111-1111-1111-111111111111",
		"exp": time.Now().Add(-time.Hour).Unix(),
	})
	_, err := c.VerifyToken(token)
	if err == nil {
		t.Fatal("expected error for expired token")
	}
	if !errors.Is(err, jwt.ErrTokenExpired) {
		t.Errorf("expected ErrTokenExpired, got %v", err)
	}
}

func TestVerifyToken_AlgNone(t *testing.T) {
	c := &Client{JWTSecret: testSecret}
	// An attacker might try alg=none to bypass signature checks.
	tok := jwt.NewWithClaims(jwt.SigningMethodNone, jwt.MapClaims{
		"sub": "22222222-2222-2222-2222-222222222222",
		"exp": time.Now().Add(time.Hour).Unix(),
	})
	token, err := tok.SignedString(jwt.UnsafeAllowNoneSignatureType)
	if err != nil {
		t.Fatalf("sign none: %v", err)
	}
	if _, err := c.VerifyToken(token); err == nil {
		t.Fatal("expected error for alg=none, got nil (critical bypass)")
	}
}

func TestVerifyToken_MissingSub(t *testing.T) {
	c := &Client{JWTSecret: testSecret}
	token := sign(t, testSecret, jwt.MapClaims{
		"exp": time.Now().Add(time.Hour).Unix(),
	})
	if _, err := c.VerifyToken(token); err == nil {
		t.Fatal("expected error for missing sub claim")
	}
}

func TestVerifyToken_Garbage(t *testing.T) {
	c := &Client{JWTSecret: testSecret}
	if _, err := c.VerifyToken("not.a.jwt"); err == nil {
		t.Fatal("expected error for garbage token")
	}
}

func TestMiddleware_NoCookieRejected(t *testing.T) {
	c := &Client{JWTSecret: testSecret}
	next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		t.Fatal("next should not run without auth")
	})
	req := httptest.NewRequest("GET", "/api/anything", nil)
	rec := httptest.NewRecorder()
	c.Middleware(next).ServeHTTP(rec, req)
	if rec.Code != http.StatusUnauthorized {
		t.Errorf("expected 401 on API without cookies, got %d", rec.Code)
	}
}
