package models

import (
	"testing"
	"time"

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

func TestUser_SetPassword(t *testing.T) {
	user := &User{}

	err := user.SetPassword("testPassword123")
	require.NoError(t, err)
	assert.NotEmpty(t, user.Password)
	assert.NotEqual(t, "testPassword123", user.Password) // Should be hashed
}

func TestUser_CheckPassword(t *testing.T) {
	user := &User{}
	err := user.SetPassword("correctpassword")
	require.NoError(t, err)

	tests := []struct {
		name     string
		password string
		expected bool
	}{
		{"correct password", "correctpassword", true},
		{"wrong password", "wrongpassword", false},
		{"empty password", "", false},
		{"similar password", "correctpassword1", false},
	}

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

func TestUser_GetFullName(t *testing.T) {
	tests := []struct {
		name      string
		user      User
		expected  string
	}{
		{
			name:     "first and last name",
			user:     User{FirstName: "John", LastName: "Doe", Username: "johndoe"},
			expected: "John Doe",
		},
		{
			name:     "first name only",
			user:     User{FirstName: "John", LastName: "", Username: "johndoe"},
			expected: "John",
		},
		{
			name:     "username fallback",
			user:     User{FirstName: "", LastName: "", Username: "johndoe"},
			expected: "johndoe",
		},
		{
			name:     "last name only returns username",
			user:     User{FirstName: "", LastName: "Doe", Username: "johndoe"},
			expected: "johndoe",
		},
	}

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

func TestUser_TableName(t *testing.T) {
	user := User{}
	assert.Equal(t, "auth_user", user.TableName())
}

func TestAuthToken_TableName(t *testing.T) {
	token := AuthToken{}
	assert.Equal(t, "user_authtoken", token.TableName())
}

func TestUserProfile_TableName(t *testing.T) {
	profile := UserProfile{}
	assert.Equal(t, "user_userprofile", profile.TableName())
}

func TestConfirmationCode_IsValid(t *testing.T) {
	now := time.Now().UTC()
	future := now.Add(1 * time.Hour)
	past := now.Add(-1 * time.Hour)

	tests := []struct {
		name     string
		code     ConfirmationCode
		expected bool
	}{
		{
			name:     "valid code",
			code:     ConfirmationCode{IsUsed: false, ExpiresAt: future},
			expected: true,
		},
		{
			name:     "used code",
			code:     ConfirmationCode{IsUsed: true, ExpiresAt: future},
			expected: false,
		},
		{
			name:     "expired code",
			code:     ConfirmationCode{IsUsed: false, ExpiresAt: past},
			expected: false,
		},
		{
			name:     "used and expired",
			code:     ConfirmationCode{IsUsed: true, ExpiresAt: past},
			expected: false,
		},
	}

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

func TestPasswordResetCode_IsValid(t *testing.T) {
	now := time.Now().UTC()
	future := now.Add(1 * time.Hour)
	past := now.Add(-1 * time.Hour)

	tests := []struct {
		name     string
		code     PasswordResetCode
		expected bool
	}{
		{
			name:     "valid code",
			code:     PasswordResetCode{Used: false, ExpiresAt: future, Attempts: 0, MaxAttempts: 5},
			expected: true,
		},
		{
			name:     "used code",
			code:     PasswordResetCode{Used: true, ExpiresAt: future, Attempts: 0, MaxAttempts: 5},
			expected: false,
		},
		{
			name:     "expired code",
			code:     PasswordResetCode{Used: false, ExpiresAt: past, Attempts: 0, MaxAttempts: 5},
			expected: false,
		},
		{
			name:     "max attempts reached",
			code:     PasswordResetCode{Used: false, ExpiresAt: future, Attempts: 5, MaxAttempts: 5},
			expected: false,
		},
		{
			name:     "attempts under max",
			code:     PasswordResetCode{Used: false, ExpiresAt: future, Attempts: 4, MaxAttempts: 5},
			expected: true,
		},
	}

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

func TestPasswordResetCode_SetAndCheckCode(t *testing.T) {
	code := &PasswordResetCode{}

	err := code.SetCode("123456")
	require.NoError(t, err)
	assert.NotEmpty(t, code.CodeHash)

	// Check correct code
	assert.True(t, code.CheckCode("123456"))

	// Check wrong code
	assert.False(t, code.CheckCode("654321"))
	assert.False(t, code.CheckCode(""))
}

func TestGenerateConfirmationCode(t *testing.T) {
	code := GenerateConfirmationCode()
	assert.Len(t, code, 6)

	// Generate multiple codes and ensure they're different
	codes := make(map[string]bool)
	for i := 0; i < 10; i++ {
		c := GenerateConfirmationCode()
		assert.Len(t, c, 6)
		codes[c] = true
	}
	// Most codes should be unique (very unlikely to have collisions)
	assert.Greater(t, len(codes), 5)
}

func TestGenerateResetToken(t *testing.T) {
	token := GenerateResetToken()
	assert.Len(t, token, 64) // 32 bytes = 64 hex chars

	// Ensure uniqueness
	token2 := GenerateResetToken()
	assert.NotEqual(t, token, token2)
}
