package usecase

import (
	"testing"
	"time"

	"backend/pkg/permission/domain/entity"

	"github.com/golang-jwt/jwt/v4"
	"github.com/stretchr/testify/assert"
)

func TestCreateAccessToken(t *testing.T) {
	tests := []struct {
		name      string
		token     entity.Token
		data      interface{}
		secretKey string
		wantErr   bool
	}{
		{
			name: "successful token creation",
			token: entity.Token{
				ID:        "test-token-id",
				ExpiresIn: int(time.Now().Add(time.Hour).Unix()),
			},
			data: map[string]string{
				"uid":  "user123",
				"role": "admin",
			},
			secretKey: "test-secret-key",
			wantErr:   false,
		},
		{
			name: "empty secret key",
			token: entity.Token{
				ID:        "test-token-id",
				ExpiresIn: int(time.Now().Add(time.Hour).Unix()),
			},
			data:      map[string]string{"uid": "user123"},
			secretKey: "",
			wantErr:   false, // JWT library will still create token with empty key
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tokenStr, err := createAccessToken(tt.token, tt.data, tt.secretKey)

			if tt.wantErr {
				assert.Error(t, err)
				assert.Empty(t, tokenStr)
			} else {
				assert.NoError(t, err)
				assert.NotEmpty(t, tokenStr)

				// Verify the token can be parsed
				token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
					return []byte(tt.secretKey), nil
				})

				if tt.secretKey != "" {
					assert.NoError(t, err)
					assert.True(t, token.Valid)

					// Check claims
					if claims, ok := token.Claims.(jwt.MapClaims); ok {
						assert.Equal(t, tt.token.ID, claims["jti"])
						assert.Equal(t, "permission", claims["iss"])
						assert.NotNil(t, claims["exp"])
						assert.NotNil(t, claims["data"])
					}
				}
			}
		})
	}
}

func TestCreateRefreshToken(t *testing.T) {
	tests := []struct {
		name        string
		accessToken string
		want        string
	}{
		{
			name:        "consistent hash generation",
			accessToken: "test-access-token",
			want:        "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", // SHA256 of "test"
		},
		{
			name:        "different token different hash",
			accessToken: "different-access-token",
			want:        "", // We'll check it's different
		},
		{
			name:        "empty token",
			accessToken: "",
			want:        "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", // SHA256 of empty string
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := createRefreshToken(tt.accessToken)

			assert.NotEmpty(t, result)
			assert.Len(t, result, 64) // SHA256 hex string length

			if tt.want != "" {
				if tt.name == "consistent hash generation" {
					// For "test" input, we know the expected hash
					testResult := createRefreshToken("test")
					assert.Equal(t, tt.want, testResult)
				} else if tt.name == "empty token" {
					assert.Equal(t, tt.want, result)
				}
			}

			// Test consistency - same input should produce same output
			result2 := createRefreshToken(tt.accessToken)
			assert.Equal(t, result, result2)
		})
	}
}

func TestParseToken(t *testing.T) {
	secretKey := "test-secret-key"

	// Create a valid token first
	token := entity.Token{
		ID:        "test-id",
		ExpiresIn: int(time.Now().Add(time.Hour).Unix()),
	}
	data := map[string]string{
		"uid":  "user123",
		"role": "admin",
	}

	validTokenStr, err := createAccessToken(token, data, secretKey)
	assert.NoError(t, err)

	tests := []struct {
		name        string
		accessToken string
		secret      string
		validate    bool
		wantErr     bool
	}{
		{
			name:        "valid token with validation",
			accessToken: validTokenStr,
			secret:      secretKey,
			validate:    true,
			wantErr:     false,
		},
		{
			name:        "valid token without validation",
			accessToken: validTokenStr,
			secret:      secretKey,
			validate:    false,
			wantErr:     false,
		},
		{
			name:        "invalid token",
			accessToken: "invalid.token.string",
			secret:      secretKey,
			validate:    true,
			wantErr:     true,
		},
		{
			name:        "wrong secret",
			accessToken: validTokenStr,
			secret:      "wrong-secret",
			validate:    true,
			wantErr:     true,
		},
		{
			name:        "empty token",
			accessToken: "",
			secret:      secretKey,
			validate:    true,
			wantErr:     true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			claims, err := parseToken(tt.accessToken, tt.secret, tt.validate)

			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, claims)

				if tt.accessToken == validTokenStr {
					assert.Equal(t, "test-id", claims["jti"])
					assert.Equal(t, "permission", claims["iss"])
				}
			}
		})
	}
}

func TestParseClaims(t *testing.T) {
	secretKey := "test-secret-key"

	// Create a valid token with data claims
	token := entity.Token{
		ID:        "test-id",
		ExpiresIn: int(time.Now().Add(time.Hour).Unix()),
	}
	data := map[string]interface{}{
		"uid":      "user123",
		"role":     "admin",
		"deviceId": "device456",
	}

	validTokenStr, err := createAccessToken(token, data, secretKey)
	assert.NoError(t, err)

	tests := []struct {
		name        string
		accessToken string
		secret      string
		validate    bool
		wantErr     bool
		expectUID   string
		expectRole  string
	}{
		{
			name:        "valid token with data claims",
			accessToken: validTokenStr,
			secret:      secretKey,
			validate:    false,
			wantErr:     false,
			expectUID:   "user123",
			expectRole:  "admin",
		},
		{
			name:        "invalid token",
			accessToken: "invalid.token",
			secret:      secretKey,
			validate:    false,
			wantErr:     true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			claims, err := ParseClaims(tt.accessToken, tt.secret, tt.validate)

			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.NotNil(t, claims)

				if tt.expectUID != "" {
					uid, exists := claims["uid"]
					assert.True(t, exists)
					assert.Equal(t, tt.expectUID, uid)
				}

				if tt.expectRole != "" {
					role, exists := claims["role"]
					assert.True(t, exists)
					assert.Equal(t, tt.expectRole, role)
				}
			}
		})
	}
}

func TestConvertMap(t *testing.T) {
	tests := []struct {
		name   string
		input  map[string]interface{}
		expect map[string]string
	}{
		{
			name: "string values",
			input: map[string]interface{}{
				"key1": "value1",
				"key2": "value2",
			},
			expect: map[string]string{
				"key1": "value1",
				"key2": "value2",
			},
		},
		{
			name: "mixed types",
			input: map[string]interface{}{
				"string": "value",
				"int":    123,
				"float":  45.67,
				"bool":   true,
			},
			expect: map[string]string{
				"string": "value",
				"int":    "123",
				"float":  "45.67",
				"bool":   "true",
			},
		},
		{
			name:   "empty map",
			input:  map[string]interface{}{},
			expect: map[string]string{},
		},
		{
			name: "nil values",
			input: map[string]interface{}{
				"nil": nil,
			},
			expect: map[string]string{
				"nil": "<nil>",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := convertMap(tt.input)
			assert.Equal(t, tt.expect, result)
		})
	}
}
