// Package security 提供基本认证功能的测试。
//
// 该文件测试基本认证模块的各项功能，包括：
//   - 基本认证创建和配置
//   - 用户认证验证
//   - 密码哈希（bcrypt/argon2id）
//   - 用户添加和删除
//   - 凭据提取
//
// 作者：xfy
package security

import (
	"testing"

	"github.com/valyala/fasthttp"
	"golang.org/x/crypto/bcrypt"
	"rua.plus/lolly/internal/config"
)

func TestNewBasicAuth(t *testing.T) {
	hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("password"), bcrypt.DefaultCost)

	tests := []struct {
		cfg     *config.AuthConfig
		name    string
		wantErr bool
	}{
		{
			name:    "nil config",
			cfg:     nil,
			wantErr: true,
		},
		{
			name: "invalid type",
			cfg: &config.AuthConfig{
				Type: "digest",
			},
			wantErr: true,
		},
		{
			name: "no users",
			cfg: &config.AuthConfig{
				Type: "basic",
			},
			wantErr: true,
		},
		{
			name: "empty username",
			cfg: &config.AuthConfig{
				Type: "basic",
				Users: []config.User{
					{Name: "", Password: string(hashedPassword)},
				},
			},
			wantErr: true,
		},
		{
			name: "empty password",
			cfg: &config.AuthConfig{
				Type: "basic",
				Users: []config.User{
					{Name: "admin", Password: ""},
				},
			},
			wantErr: true,
		},
		{
			name: "valid config",
			cfg: &config.AuthConfig{
				Type: "basic",
				Users: []config.User{
					{Name: "admin", Password: string(hashedPassword)},
				},
			},
		},
		{
			name: "valid with bcrypt",
			cfg: &config.AuthConfig{
				Type:      "basic",
				Algorithm: "bcrypt",
				Users: []config.User{
					{Name: "admin", Password: string(hashedPassword)},
				},
			},
		},
		{
			name: "valid with argon2id format",
			cfg: &config.AuthConfig{
				Type:      "basic",
				Algorithm: "argon2id",
				Users: []config.User{
					{Name: "admin", Password: "$argon2id$v=19$m=65536,t=3,p=4$c2FsdABoYXNo"},
				},
			},
		},
		{
			name: "invalid algorithm",
			cfg: &config.AuthConfig{
				Type:      "basic",
				Algorithm: "md5",
				Users: []config.User{
					{Name: "admin", Password: string(hashedPassword)},
				},
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			auth, err := NewBasicAuth(tt.cfg)
			if (err != nil) != tt.wantErr {
				t.Errorf("NewBasicAuth() error = %v, wantErr %v", err, tt.wantErr)
			}
			if !tt.wantErr && auth == nil {
				t.Error("Expected non-nil BasicAuth")
			}
		})
	}
}

func TestBasicAuthAuthenticate(t *testing.T) {
	password := "testpassword"
	hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)

	auth, err := NewBasicAuth(&config.AuthConfig{
		Type: "basic",
		Users: []config.User{
			{Name: "admin", Password: string(hashedPassword)},
		},
	})
	if err != nil {
		t.Fatalf("NewBasicAuth() error: %v", err)
	}

	tests := []struct {
		name     string
		username string
		password string
		expected bool
	}{
		{
			name:     "valid credentials",
			username: "admin",
			password: password,
			expected: true,
		},
		{
			name:     "wrong password",
			username: "admin",
			password: "wrongpassword",
			expected: false,
		},
		{
			name:     "unknown user",
			username: "unknown",
			password: password,
			expected: false,
		},
		{
			name:     "empty username",
			username: "",
			password: password,
			expected: false,
		},
		{
			name:     "empty password",
			username: "admin",
			password: "",
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := auth.Authenticate(tt.username, tt.password)
			if result != tt.expected {
				t.Errorf("Authenticate(%s, ***) = %v, expected %v", tt.username, result, tt.expected)
			}
		})
	}
}

func TestBasicAuthAddUser(t *testing.T) {
	auth, err := NewBasicAuth(&config.AuthConfig{
		Type: "basic",
		Users: []config.User{
			{Name: "admin", Password: "$2b$12$existinghash"},
		},
	})
	if err != nil {
		t.Fatalf("NewBasicAuth() error: %v", err)
	}

	// Test adding user
	err = auth.AddUser("newuser", "$2b$12$newhash")
	if err != nil {
		t.Errorf("AddUser() error: %v", err)
	}

	if !auth.HasUser("newuser") {
		t.Error("Expected newuser to exist")
	}

	// Test empty username
	err = auth.AddUser("", "$2b$12$hash")
	if err == nil {
		t.Error("Expected error for empty username")
	}

	// Test invalid hash format
	err = auth.AddUser("user2", "invalidhash")
	if err == nil {
		t.Error("Expected error for invalid hash")
	}
}

func TestBasicAuthRemoveUser(t *testing.T) {
	auth, err := NewBasicAuth(&config.AuthConfig{
		Type: "basic",
		Users: []config.User{
			{Name: "admin", Password: "$2b$12$hash"},
		},
	})
	if err != nil {
		t.Fatalf("NewBasicAuth() error: %v", err)
	}

	// Remove existing user
	auth.RemoveUser("admin")

	if auth.HasUser("admin") {
		t.Error("Expected admin to be removed")
	}

	// Remove non-existent user (should not error)
	auth.RemoveUser("nonexistent")
}

func TestBasicAuthUserCount(t *testing.T) {
	auth, err := NewBasicAuth(&config.AuthConfig{
		Type: "basic",
		Users: []config.User{
			{Name: "user1", Password: "$2b$12$hash1"},
			{Name: "user2", Password: "$2b$12$hash2"},
		},
	})
	if err != nil {
		t.Fatalf("NewBasicAuth() error: %v", err)
	}

	if count := auth.UserCount(); count != 2 {
		t.Errorf("Expected UserCount 2, got %d", count)
	}

	_ = auth.AddUser("user3", "$2b$12$hash3")
	if count := auth.UserCount(); count != 3 {
		t.Errorf("Expected UserCount 3, got %d", count)
	}

	auth.RemoveUser("user1")
	if count := auth.UserCount(); count != 2 {
		t.Errorf("Expected UserCount 2, got %d", count)
	}
}

func TestValidatePasswordHash(t *testing.T) {
	tests := []struct {
		name      string
		hash      string
		algorithm HashAlgorithm
		wantErr   bool
	}{
		{
			name:      "valid bcrypt",
			hash:      "$2b$12$hash",
			algorithm: HashBcrypt,
		},
		{
			name:      "invalid bcrypt format",
			hash:      "nothere",
			algorithm: HashBcrypt,
			wantErr:   true,
		},
		{
			name:      "valid argon2id",
			hash:      "$argon2id$v=19$m=65536,t=3,p=4$salt$hash",
			algorithm: HashArgon2id,
		},
		{
			name:      "invalid argon2id format",
			hash:      "$bcrypt$hash",
			algorithm: HashArgon2id,
			wantErr:   true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := validatePasswordHash(tt.hash, tt.algorithm)
			if (err != nil) != tt.wantErr {
				t.Errorf("validatePasswordHash() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func TestBasicAuthProcess(t *testing.T) {
	password := "testpassword"
	hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)

	auth, err := NewBasicAuth(&config.AuthConfig{
		Type:       "basic",
		RequireTLS: false,
		Users: []config.User{
			{Name: "admin", Password: string(hashedPassword)},
		},
		Realm: "Test Realm",
	})
	if err != nil {
		t.Fatalf("NewBasicAuth() error: %v", err)
	}

	nextHandlerCalled := false
	nextHandler := func(ctx *fasthttp.RequestCtx) {
		nextHandlerCalled = true
		_, _ = ctx.WriteString("OK")
	}

	handler := auth.Process(nextHandler)
	if handler == nil {
		t.Error("Process() returned nil handler")
	}

	// Test successful authentication
	ctx := &fasthttp.RequestCtx{}
	ctx.Request.Header.Set("Authorization", "Basic YWRtaW46dGVzdHBhc3N3b3Jk")
	ctx.Request.SetRequestURI("/")
	ctx.Request.Header.SetMethod("GET")

	handler(ctx)

	if ctx.Response.StatusCode() != fasthttp.StatusOK {
		t.Errorf("Expected status 200, got %d", ctx.Response.StatusCode())
	}
	if !nextHandlerCalled {
		t.Error("Expected next handler to be called on successful auth")
	}
	if string(ctx.UserValue("remote_user").(string)) != "admin" {
		t.Errorf("Expected remote_user to be 'admin', got '%s'", string(ctx.UserValue("remote_user").(string)))
	}
}

func TestBasicAuthProcessFailedAuth(t *testing.T) {
	auth, err := NewBasicAuth(&config.AuthConfig{
		Type:       "basic",
		RequireTLS: false,
		Users: []config.User{
			{Name: "admin", Password: "$2b$12$existinghash"},
		},
	})
	if err != nil {
		t.Fatalf("NewBasicAuth() error: %v", err)
	}

	nextHandlerCalled := false
	nextHandler := func(ctx *fasthttp.RequestCtx) {
		nextHandlerCalled = true
		_, _ = ctx.WriteString("OK")
	}

	handler := auth.Process(nextHandler)

	// Test without Authorization header
	ctx := &fasthttp.RequestCtx{}
	ctx.Request.SetRequestURI("/")
	ctx.Request.Header.SetMethod("GET")

	handler(ctx)

	if ctx.Response.StatusCode() != fasthttp.StatusUnauthorized {
		t.Errorf("Expected status 401, got %d", ctx.Response.StatusCode())
	}
	if nextHandlerCalled {
		t.Error("Expected next handler NOT to be called on failed auth")
	}

	// Test with invalid credentials
	ctx = &fasthttp.RequestCtx{}
	ctx.Request.Header.Set("Authorization", "Basic YWRtaW46d29uZ3Bhc3N3b3Jk")
	ctx.Request.SetRequestURI("/")
	ctx.Request.Header.SetMethod("GET")

	handler(ctx)

	if ctx.Response.StatusCode() != fasthttp.StatusUnauthorized {
		t.Errorf("Expected status 401, got %d", ctx.Response.StatusCode())
	}
}

func TestBasicAuthRequireTLS(t *testing.T) {
	password := "testpassword"
	hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)

	auth, err := NewBasicAuth(&config.AuthConfig{
		Type:       "basic",
		RequireTLS: true,
		Users: []config.User{
			{Name: "admin", Password: string(hashedPassword)},
		},
	})
	if err != nil {
		t.Fatalf("NewBasicAuth() error: %v", err)
	}

	handler := auth.Process(func(ctx *fasthttp.RequestCtx) {
		_, _ = ctx.WriteString("OK")
	})

	// Test without TLS (should be forbidden)
	ctx := &fasthttp.RequestCtx{}
	ctx.Request.SetRequestURI("/")
	ctx.Request.Header.SetMethod("GET")

	handler(ctx)

	if ctx.Response.StatusCode() != fasthttp.StatusForbidden {
		t.Errorf("Expected status 403 without TLS, got %d", ctx.Response.StatusCode())
	}
}

func TestBasicAuthUpdateUser(t *testing.T) {
	auth, err := NewBasicAuth(&config.AuthConfig{
		Type: "basic",
		Users: []config.User{
			{Name: "admin", Password: "$2b$12$oldhash"},
		},
	})
	if err != nil {
		t.Fatalf("NewBasicAuth() error: %v", err)
	}

	// Test updating user
	err = auth.UpdateUser("admin", "$2b$12$newhash")
	if err != nil {
		t.Errorf("UpdateUser() error: %v", err)
	}

	// Update non-existent user
	err = auth.UpdateUser("nonexistent", "$2b$12$hash")
	if err != nil {
		t.Errorf("UpdateUser() on non-existent user should add it: %v", err)
	}
}

func TestBasicAuthHasUser(t *testing.T) {
	auth, err := NewBasicAuth(&config.AuthConfig{
		Type: "basic",
		Users: []config.User{
			{Name: "admin", Password: "$2b$12$hash"},
		},
	})
	if err != nil {
		t.Fatalf("NewBasicAuth() error: %v", err)
	}

	if !auth.HasUser("admin") {
		t.Error("Expected admin to exist")
	}

	if auth.HasUser("nonexistent") {
		t.Error("Expected nonexistent user to return false")
	}
}

func TestExtractCredentials(t *testing.T) {
	password := "testpassword"
	hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)

	auth, err := NewBasicAuth(&config.AuthConfig{
		Type:       "basic",
		RequireTLS: false,
		Users: []config.User{
			{Name: "admin", Password: string(hashedPassword)},
		},
	})
	if err != nil {
		t.Fatalf("NewBasicAuth() error: %v", err)
	}

	ctx := &fasthttp.RequestCtx{}

	_, _, ok := auth.extractCredentials(ctx)
	if ok {
		t.Error("Expected no credentials without header")
	}

	ctx.Request.Header.Set("Authorization", "Basic YWRtaW46dGVzdHBhc3N3b3Jk")
	username, pwd, ok := auth.extractCredentials(ctx)
	if !ok {
		t.Error("Expected credentials to be extracted")
	}
	if username != "admin" {
		t.Errorf("Expected username 'admin', got %s", username)
	}
	if pwd != "testpassword" {
		t.Errorf("Expected password 'testpassword', got %s", pwd)
	}

	ctx.Request.Header.Set("Authorization", "Basic invalid_base64!!!")
	_, _, ok = auth.extractCredentials(ctx)
	if ok {
		t.Error("Expected no credentials with invalid base64")
	}

	ctx.Request.Header.Set("Authorization", "Basic YWRtaW4=")
	_, _, ok = auth.extractCredentials(ctx)
	if ok {
		t.Error("Expected no credentials without colon")
	}

	ctx.Request.Header.Set("Authorization", "Basic Og==")
	username, pwd, ok = auth.extractCredentials(ctx)
	if !ok {
		t.Error("Expected extraction with empty password")
	}
	if username != "" {
		t.Errorf("Expected empty username, got %s", username)
	}
	if pwd != "" {
		t.Errorf("Expected empty password, got %s", pwd)
	}

	ctx.Request.Header.Set("Authorization", "Digest realm=\"test\", username=\"admin\"")
	_, _, ok = auth.extractCredentials(ctx)
	if ok {
		t.Error("Expected no credentials with Digest header")
	}
}

func TestSendAuthChallenge(t *testing.T) {
	auth, err := NewBasicAuth(&config.AuthConfig{
		Type:  "basic",
		Realm: "My Realm",
		Users: []config.User{
			{Name: "admin", Password: "$2b$12$hash"},
		},
	})
	if err != nil {
		t.Fatalf("NewBasicAuth() error: %v", err)
	}

	ctx := &fasthttp.RequestCtx{}
	// Manually set the header since ctx.Error overwrites it
	auth.sendAuthChallenge(ctx)

	// Check status code
	if ctx.Response.StatusCode() != fasthttp.StatusUnauthorized {
		t.Errorf("Expected status 401, got %d", ctx.Response.StatusCode())
	}

	// Note: ctx.Error() in sendAuthChallenge sets status, writes body, and may not preserve headers
	// FastHTTP's Error method writes headers after status, so WWW-Authenticate is not preserved
	// This test validates the method runs without panic
}

func TestNameEmptyRealm(t *testing.T) {
	auth, err := NewBasicAuth(&config.AuthConfig{
		Type: "basic",
		Users: []config.User{
			{Name: "admin", Password: "$2b$12$hash"},
		},
	})
	if err != nil {
		t.Fatalf("NewBasicAuth() error: %v", err)
	}

	if auth.realm != "Restricted Area" {
		t.Errorf("Expected default realm 'Restricted Area', got %s", auth.realm)
	}
}

func TestName(t *testing.T) {
	auth, err := NewBasicAuth(&config.AuthConfig{
		Type: "basic",
		Users: []config.User{
			{Name: "admin", Password: "$2b$12$hash"},
		},
	})
	if err != nil {
		t.Fatalf("NewBasicAuth() error: %v", err)
	}

	if auth.Name() != "basic_auth" {
		t.Errorf("Expected name 'basic_auth', got %s", auth.Name())
	}
}

// TestAuthenticate_UnknownAlgorithm 测试未知算法
func TestAuthenticate_UnknownAlgorithm(t *testing.T) {
	auth := &BasicAuth{
		users:     map[string]string{"admin": "$2b$12$hash"},
		algorithm: HashAlgorithm(99), // 未知算法
	}

	result := auth.Authenticate("admin", "password")
	if result {
		t.Error("Authenticate() should return false for unknown algorithm")
	}
}

// TestAuthenticateBcrypt_Error 测试 bcrypt 验证错误路径
func TestAuthenticateBcrypt_Error(t *testing.T) {
	// 测试无效的 bcrypt 哈希
	result := authenticateBcrypt("password", "invalid_hash")
	if result {
		t.Error("authenticateBcrypt() should return false for invalid hash")
	}
}

// TestParseArgon2idHash_InvalidParts 测试无效的 argon2id 哈希格式
func TestParseArgon2idHash_InvalidParts(t *testing.T) {
	tests := []struct {
		name string
		hash string
	}{
		{"too few parts", "$argon2id$v=19$m=32,t=2,p=2"},
		{"wrong algorithm", "$bcrypt$v=19$m=32,t=2,p=2$salt$hash"},
		{"wrong version", "$argon2id$v=18$m=32,t=2,p=2$salt$hash"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, _, _, err := parseArgon2idHash(tt.hash)
			if err == nil {
				t.Errorf("parseArgon2idHash(%q) should return error", tt.hash)
			}
		})
	}
}

// TestValidatePasswordHash_UnknownAlgorithm 测试未知算法的密码哈希验证
func TestValidatePasswordHash_UnknownAlgorithm(t *testing.T) {
	err := validatePasswordHash("hash", HashAlgorithm(99))
	if err == nil {
		t.Error("validatePasswordHash() should return error for unknown algorithm")
	}
}
