package auth

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestDecodeWalletSignatureRejectsMalformedValues(t *testing.T) {
	_, err := decodeWalletSignature("deadbeef")
	require.ErrorContains(t, err, "signature must start with 0x")

	_, err = decodeWalletSignature("0x1234")
	require.ErrorContains(t, err, "invalid signature length")
}

func TestValidateJWTReturnsClaimsWhenDBUnavailable(t *testing.T) {
	secret := []byte("test-secret")
	auth := NewWalletAuth(nil, secret)

	token, _, err := auth.generateJWT("0x4A666F96fC8764181194447A7dFdb7d471b301C8", 4)
	require.NoError(t, err)

	address, track, err := auth.ValidateJWT(token)
	require.NoError(t, err)
	require.Equal(t, "0x4A666F96fC8764181194447A7dFdb7d471b301C8", address)
	require.Equal(t, 4, track)
}

func TestTokenTTLForTrack4IsShort(t *testing.T) {
	// Track 4 (operator) must have a TTL <= 1h — that is the headline
	// tightening promised by completion criterion 3 (JWT hygiene).
	ttl := tokenTTLFor(4)
	require.LessOrEqual(t, ttl, time.Hour, "track 4 TTL must be <= 1h")
	require.Greater(t, ttl, time.Duration(0), "track 4 TTL must be positive")
}

func TestTokenTTLForTrack1Track2Track3AreReasonable(t *testing.T) {
	// Non-operator tracks are allowed longer sessions, but still bounded
	// at 12h so a stale laptop tab doesn't carry a week-old token.
	for _, track := range []int{1, 2, 3} {
		ttl := tokenTTLFor(track)
		require.Greater(t, ttl, time.Duration(0), "track %d TTL must be > 0", track)
		require.LessOrEqual(t, ttl, 12*time.Hour, "track %d TTL must be <= 12h", track)
	}
}

func TestGeneratedJWTCarriesJTIClaim(t *testing.T) {
	// Revocation keys on jti. A token issued without one is unrevokable
	// and must not be produced.
	a := NewWalletAuth(nil, []byte("test-secret"))
	token, _, err := a.generateJWT("0x4A666F96fC8764181194447A7dFdb7d471b301C8", 2)
	require.NoError(t, err)

	jti, err := a.jtiFromToken(token)
	require.NoError(t, err)
	require.NotEmpty(t, jti, "generated JWT must carry a jti claim")
	require.Len(t, jti, 32, "jti should be 16 random bytes hex-encoded (32 chars)")
}

func TestGeneratedJWTExpIsTrackAppropriate(t *testing.T) {
	a := NewWalletAuth(nil, []byte("test-secret"))
	for _, track := range []int{1, 2, 3, 4} {
		_, expiresAt, err := a.generateJWT("0x4A666F96fC8764181194447A7dFdb7d471b301C8", track)
		require.NoError(t, err)
		want := tokenTTLFor(track)
		// allow a couple-second slack for test execution
		actual := time.Until(expiresAt)
		require.InDelta(t, want.Seconds(), actual.Seconds(), 5.0,
			"track %d exp should be ~%s from now, got %s", track, want, actual)
	}
}

func TestRevokeJWTWithoutDBReturnsError(t *testing.T) {
	// With w.db == nil, revocation has nowhere to write — the call must
	// fail loudly so callers don't silently assume a token was revoked.
	a := NewWalletAuth(nil, []byte("test-secret"))
	token, _, err := a.generateJWT("0x4A666F96fC8764181194447A7dFdb7d471b301C8", 4)
	require.NoError(t, err)

	err = a.RevokeJWT(context.Background(), token, "test")
	require.Error(t, err)
	require.Contains(t, err.Error(), "no database")
}
