package token

import (
	"testing"

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

func TestType_String(t *testing.T) {
	tests := []struct {
		name      string
		tokenType Type
		expected  string
	}{
		{
			name:      "bearer type",
			tokenType: TypeBearer,
			expected:  "Bearer",
		},
		{
			name:      "basic type",
			tokenType: TypeBasic,
			expected:  "Basic",
		},
		{
			name:      "custom type",
			tokenType: Type("Custom"),
			expected:  "Custom",
		},
		{
			name:      "empty type",
			tokenType: Type(""),
			expected:  "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := tt.tokenType.String()
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestType_IsValid(t *testing.T) {
	tests := []struct {
		name      string
		tokenType Type
		expected  bool
	}{
		{
			name:      "bearer is valid",
			tokenType: TypeBearer,
			expected:  true,
		},
		{
			name:      "basic is valid",
			tokenType: TypeBasic,
			expected:  true,
		},
		{
			name:      "invalid type",
			tokenType: Type("Invalid"),
			expected:  false,
		},
		{
			name:      "empty type",
			tokenType: Type(""),
			expected:  false,
		},
		{
			name:      "lowercase bearer",
			tokenType: Type("bearer"),
			expected:  false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := tt.tokenType.IsValid()
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestType_Constants(t *testing.T) {
	t.Run("verify constant values", func(t *testing.T) {
		assert.Equal(t, "Bearer", TypeBearer.String())
		assert.Equal(t, "Basic", TypeBasic.String())
	})

	t.Run("verify constants are valid", func(t *testing.T) {
		assert.True(t, TypeBearer.IsValid())
		assert.True(t, TypeBasic.IsValid())
	})
}

func TestRedisKeyPrefixes(t *testing.T) {
	t.Run("verify key prefix constants", func(t *testing.T) {
		assert.Equal(t, "access_token:", AccessTokenKeyPrefix)
		assert.Equal(t, "refresh_token:", RefreshTokenKeyPrefix)
		assert.Equal(t, "one_time_token:", OneTimeTokenKeyPrefix)
		assert.Equal(t, "uid_tokens:", UIDTokenKeyPrefix)
		assert.Equal(t, "device_tokens:", DeviceTokenKeyPrefix)
		assert.Equal(t, "ticket:", TicketKeyPrefix)
		assert.Equal(t, "blacklist:", BlacklistKeyPrefix)
	})
}

func TestGetAccessTokenRedisKey(t *testing.T) {
	tests := []struct {
		name     string
		tokenID  string
		expected string
	}{
		{
			name:     "normal token ID",
			tokenID:  "token123",
			expected: "access_token:token123",
		},
		{
			name:     "UUID token ID",
			tokenID:  "550e8400-e29b-41d4-a716-446655440000",
			expected: "access_token:550e8400-e29b-41d4-a716-446655440000",
		},
		{
			name:     "empty token ID",
			tokenID:  "",
			expected: "access_token:",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := GetAccessTokenRedisKey(tt.tokenID)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestRefreshTokenRedisKey(t *testing.T) {
	tests := []struct {
		name     string
		tokenID  string
		expected string
	}{
		{
			name:     "normal token ID",
			tokenID:  "refresh123",
			expected: "refresh_token:refresh123",
		},
		{
			name:     "hash token ID",
			tokenID:  "a1b2c3d4e5f6",
			expected: "refresh_token:a1b2c3d4e5f6",
		},
		{
			name:     "empty token ID",
			tokenID:  "",
			expected: "refresh_token:",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := RefreshTokenRedisKey(tt.tokenID)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestUIDTokenRedisKey(t *testing.T) {
	tests := []struct {
		name     string
		uid      string
		expected string
	}{
		{
			name:     "normal UID",
			uid:      "user123",
			expected: "uid_tokens:user123",
		},
		{
			name:     "UUID UID",
			uid:      "550e8400-e29b-41d4-a716-446655440000",
			expected: "uid_tokens:550e8400-e29b-41d4-a716-446655440000",
		},
		{
			name:     "empty UID",
			uid:      "",
			expected: "uid_tokens:",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := UIDTokenRedisKey(tt.uid)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestDeviceTokenRedisKey(t *testing.T) {
	tests := []struct {
		name     string
		deviceID string
		expected string
	}{
		{
			name:     "normal device ID",
			deviceID: "device123",
			expected: "device_tokens:device123",
		},
		{
			name:     "UUID device ID",
			deviceID: "550e8400-e29b-41d4-a716-446655440000",
			expected: "device_tokens:550e8400-e29b-41d4-a716-446655440000",
		},
		{
			name:     "empty device ID",
			deviceID: "",
			expected: "device_tokens:",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := DeviceTokenRedisKey(tt.deviceID)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestGetBlacklistRedisKey(t *testing.T) {
	tests := []struct {
		name     string
		jti      string
		expected string
	}{
		{
			name:     "normal JTI",
			jti:      "jti123",
			expected: "blacklist:jti123",
		},
		{
			name:     "UUID JTI",
			jti:      "550e8400-e29b-41d4-a716-446655440000",
			expected: "blacklist:550e8400-e29b-41d4-a716-446655440000",
		},
		{
			name:     "empty JTI",
			jti:      "",
			expected: "blacklist:",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := GetBlacklistRedisKey(tt.jti)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestGetUIDTokenRedisKey(t *testing.T) {
	tests := []struct {
		name     string
		uid      string
		expected string
	}{
		{
			name:     "normal UID",
			uid:      "user456",
			expected: "uid_tokens:user456",
		},
		{
			name:     "empty UID",
			uid:      "",
			expected: "uid_tokens:",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := GetUIDTokenRedisKey(tt.uid)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestDefaultExpirationTimes(t *testing.T) {
	t.Run("verify default expiration constants", func(t *testing.T) {
		assert.Equal(t, int64(15*60), int64(DefaultAccessTokenExpiry))
		assert.Equal(t, int64(7*24*3600), int64(DefaultRefreshTokenExpiry))
		assert.Equal(t, int64(5*60), int64(DefaultOneTimeTokenExpiry))
	})

	t.Run("verify expiration times are reasonable", func(t *testing.T) {
		assert.Greater(t, int64(DefaultAccessTokenExpiry), int64(0))
		assert.Greater(t, int64(DefaultRefreshTokenExpiry), int64(DefaultAccessTokenExpiry))
		assert.Greater(t, int64(DefaultOneTimeTokenExpiry), int64(0))
		assert.Less(t, int64(DefaultOneTimeTokenExpiry), int64(DefaultAccessTokenExpiry))
	})
}

func TestTokenLimits(t *testing.T) {
	t.Run("verify token limit constants", func(t *testing.T) {
		assert.Equal(t, 10, MaxTokensPerUser)
		assert.Equal(t, 5, MaxTokensPerDevice)
	})

	t.Run("verify limits are reasonable", func(t *testing.T) {
		assert.Greater(t, MaxTokensPerUser, 0)
		assert.Greater(t, MaxTokensPerDevice, 0)
		assert.GreaterOrEqual(t, MaxTokensPerUser, MaxTokensPerDevice)
	})
}

func TestKeyPrefixUniqueness(t *testing.T) {
	t.Run("all key prefixes should be unique", func(t *testing.T) {
		prefixes := []string{
			AccessTokenKeyPrefix,
			RefreshTokenKeyPrefix,
			OneTimeTokenKeyPrefix,
			UIDTokenKeyPrefix,
			DeviceTokenKeyPrefix,
			TicketKeyPrefix,
			BlacklistKeyPrefix,
		}

		seen := make(map[string]bool)
		for _, prefix := range prefixes {
			assert.False(t, seen[prefix], "duplicate prefix found: %s", prefix)
			seen[prefix] = true
		}

		assert.Equal(t, len(prefixes), len(seen))
	})
}
