package entity

import (
	"testing"
	"time"

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

func TestToken_IsExpired(t *testing.T) {
	tests := []struct {
		name     string
		token    *Token
		expected bool
	}{
		{
			name: "expired token",
			token: &Token{
				ID:        "test-id",
				UID:       "test-uid",
				ExpiresIn: int(time.Now().Add(-time.Hour).Unix()),
			},
			expected: true,
		},
		{
			name: "valid token",
			token: &Token{
				ID:        "test-id",
				UID:       "test-uid",
				ExpiresIn: int(time.Now().Add(time.Hour).Unix()),
			},
			expected: false,
		},
		{
			name: "token expiring now",
			token: &Token{
				ID:        "test-id",
				UID:       "test-uid",
				ExpiresIn: int(time.Now().Unix()) - 1,
			},
			expected: true,
		},
	}

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

func TestToken_IsRefreshExpired(t *testing.T) {
	tests := []struct {
		name     string
		token    *Token
		expected bool
	}{
		{
			name: "expired refresh token",
			token: &Token{
				ID:               "test-id",
				UID:              "test-uid",
				RefreshExpiresIn: int(time.Now().Add(-time.Hour).Unix()),
			},
			expected: true,
		},
		{
			name: "valid refresh token",
			token: &Token{
				ID:               "test-id",
				UID:              "test-uid",
				RefreshExpiresIn: int(time.Now().Add(time.Hour).Unix()),
			},
			expected: false,
		},
	}

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

func TestToken_RedisRefreshExpiredSec(t *testing.T) {
	tests := []struct {
		name     string
		token    *Token
		expected int
	}{
		{
			name: "token with future expiry",
			token: &Token{
				ID:               "test-id",
				UID:              "test-uid",
				RefreshExpiresIn: int(time.Now().Add(time.Hour).Unix()),
			},
			expected: 3600, // Approximately 1 hour in seconds
		},
		{
			name: "token already expired",
			token: &Token{
				ID:               "test-id",
				UID:              "test-uid",
				RefreshExpiresIn: int(time.Now().Add(-time.Hour).Unix()),
			},
			expected: 0,
		},
		{
			name: "token expiring now",
			token: &Token{
				ID:               "test-id",
				UID:              "test-uid",
				RefreshExpiresIn: int(time.Now().Unix()),
			},
			expected: 0,
		},
	}

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

			if tt.expected == 0 {
				assert.Equal(t, 0, result)
			} else {
				// Allow some margin for test execution time
				assert.InDelta(t, tt.expected, result, 5)
			}
		})
	}
}

func TestToken_Validate(t *testing.T) {
	tests := []struct {
		name        string
		token       *Token
		wantErr     bool
		expectedErr error
	}{
		{
			name: "valid token",
			token: &Token{
				ID:          "test-id",
				UID:         "test-uid",
				AccessToken: "test-access-token",
				ExpiresIn:   int(time.Now().Add(time.Hour).Unix()),
			},
			wantErr: false,
		},
		{
			name: "missing ID",
			token: &Token{
				ID:          "",
				UID:         "test-uid",
				AccessToken: "test-access-token",
			},
			wantErr:     true,
			expectedErr: ErrInvalidTokenID,
		},
		{
			name: "missing UID",
			token: &Token{
				ID:          "test-id",
				UID:         "",
				AccessToken: "test-access-token",
			},
			wantErr:     true,
			expectedErr: ErrInvalidUID,
		},
		{
			name: "missing AccessToken",
			token: &Token{
				ID:          "test-id",
				UID:         "test-uid",
				AccessToken: "",
			},
			wantErr:     true,
			expectedErr: ErrInvalidAccessToken,
		},
		{
			name: "all fields missing",
			token: &Token{
				ID:          "",
				UID:         "",
				AccessToken: "",
			},
			wantErr:     true,
			expectedErr: ErrInvalidTokenID,
		},
	}

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

			if tt.wantErr {
				assert.Error(t, err)
				if tt.expectedErr != nil {
					assert.Equal(t, tt.expectedErr, err)
				}
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func TestTicket(t *testing.T) {
	t.Run("ticket with data", func(t *testing.T) {
		ticket := Ticket{
			Data: map[string]string{
				"uid":  "user123",
				"role": "admin",
			},
			Token: Token{
				ID:          "token123",
				UID:         "user123",
				AccessToken: "access-token",
			},
		}

		assert.NotNil(t, ticket.Data)
		assert.Equal(t, "user123", ticket.Data["uid"])
		assert.Equal(t, "admin", ticket.Data["role"])
		assert.Equal(t, "token123", ticket.Token.ID)
	})

	t.Run("empty ticket", func(t *testing.T) {
		ticket := Ticket{}

		assert.Nil(t, ticket.Data)
		assert.Empty(t, ticket.Token.ID)
	})
}

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

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			token := &Token{
				ID:          "test-id",
				UID:         "test-uid",
				DeviceID:    tt.deviceID,
				AccessToken: "test-token",
			}

			assert.Equal(t, tt.deviceID, token.DeviceID)
		})
	}
}

func TestToken_RefreshToken(t *testing.T) {
	tests := []struct {
		name         string
		refreshToken string
	}{
		{
			name:         "with refresh token",
			refreshToken: "refresh-token-123",
		},
		{
			name:         "empty refresh token",
			refreshToken: "",
		},
		{
			name:         "long refresh token",
			refreshToken: "very-long-refresh-token-with-hash-abcdef1234567890",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			token := &Token{
				ID:           "test-id",
				UID:          "test-uid",
				AccessToken:  "access-token",
				RefreshToken: tt.refreshToken,
			}

			assert.Equal(t, tt.refreshToken, token.RefreshToken)
		})
	}
}

func TestToken_Timestamps(t *testing.T) {
	now := time.Now()
	token := &Token{
		ID:              "test-id",
		UID:             "test-uid",
		AccessToken:     "access-token",
		AccessCreateAt:  now,
		RefreshCreateAt: now.Add(time.Second),
	}

	assert.Equal(t, now, token.AccessCreateAt)
	assert.True(t, token.RefreshCreateAt.After(token.AccessCreateAt))
}
