package repository

import (
	"context"
	"testing"

	"gorm.io/gorm"

	"github.com/user-management-system/internal/domain"
)

func setupTestDB(t *testing.T) *gorm.DB {
	return openTestDB(t)
}

// TestUserRepository_Create 测试创建用户
func TestUserRepository_Create(t *testing.T) {
	db := setupTestDB(t)
	repo := NewUserRepository(db)
	ctx := context.Background()

	user := &domain.User{
		Username: "testuser",
		Email:    domain.StrPtr("test@example.com"),
		Phone:    domain.StrPtr("13800138000"),
		Password: "hashedpassword",
		Status:   domain.UserStatusActive,
	}

	if err := repo.Create(ctx, user); err != nil {
		t.Fatalf("Create() error = %v", err)
	}
	if user.ID == 0 {
		t.Error("创建后用户ID不应为0")
	}
}

// TestUserRepository_GetByUsername 测试根据用户名查询
func TestUserRepository_GetByUsername(t *testing.T) {
	db := setupTestDB(t)
	repo := NewUserRepository(db)
	ctx := context.Background()

	user := &domain.User{
		Username: "findme",
		Email:    domain.StrPtr("findme@example.com"),
		Password: "hash",
		Status:   domain.UserStatusActive,
	}
	repo.Create(ctx, user)

	found, err := repo.GetByUsername(ctx, "findme")
	if err != nil {
		t.Fatalf("GetByUsername() error = %v", err)
	}
	if found.Username != "findme" {
		t.Errorf("Username = %v, want findme", found.Username)
	}

	_, err = repo.GetByUsername(ctx, "notexist")
	if err == nil {
		t.Error("查找不存在的用户应返回错误")
	}
}

// TestUserRepository_GetByEmail 测试根据邮箱查询
func TestUserRepository_GetByEmail(t *testing.T) {
	db := setupTestDB(t)
	repo := NewUserRepository(db)
	ctx := context.Background()

	user := &domain.User{
		Username: "emailuser",
		Email:    domain.StrPtr("email@example.com"),
		Password: "hash",
		Status:   domain.UserStatusActive,
	}
	repo.Create(ctx, user)

	found, err := repo.GetByEmail(ctx, "email@example.com")
	if err != nil {
		t.Fatalf("GetByEmail() error = %v", err)
	}
	if domain.DerefStr(found.Email) != "email@example.com" {
		t.Errorf("Email = %v, want email@example.com", domain.DerefStr(found.Email))
	}
}

// TestUserRepository_Update 测试更新用户
func TestUserRepository_Update(t *testing.T) {
	db := setupTestDB(t)
	repo := NewUserRepository(db)
	ctx := context.Background()

	user := &domain.User{
		Username: "updateme",
		Email:    domain.StrPtr("update@example.com"),
		Password: "hash",
		Status:   domain.UserStatusActive,
	}
	repo.Create(ctx, user)

	user.Nickname = "已更新"
	if err := repo.Update(ctx, user); err != nil {
		t.Fatalf("Update() error = %v", err)
	}

	found, _ := repo.GetByID(ctx, user.ID)
	if found.Nickname != "已更新" {
		t.Errorf("Nickname = %v, want 已更新", found.Nickname)
	}
}

// TestUserRepository_Delete 测试删除用户
func TestUserRepository_Delete(t *testing.T) {
	db := setupTestDB(t)
	repo := NewUserRepository(db)
	ctx := context.Background()

	user := &domain.User{
		Username: "deleteme",
		Email:    domain.StrPtr("delete@example.com"),
		Password: "hash",
		Status:   domain.UserStatusActive,
	}
	repo.Create(ctx, user)

	if err := repo.Delete(ctx, user.ID); err != nil {
		t.Fatalf("Delete() error = %v", err)
	}

	_, err := repo.GetByID(ctx, user.ID)
	if err == nil {
		t.Error("删除后查询应返回错误")
	}
}

// TestUserRepository_ExistsBy 测试存在性检查
func TestUserRepository_ExistsBy(t *testing.T) {
	db := setupTestDB(t)
	repo := NewUserRepository(db)
	ctx := context.Background()

	user := &domain.User{
		Username: "existsuser",
		Email:    domain.StrPtr("exists@example.com"),
		Phone:    domain.StrPtr("13900139000"),
		Password: "hash",
		Status:   domain.UserStatusActive,
	}
	repo.Create(ctx, user)

	ok, _ := repo.ExistsByUsername(ctx, "existsuser")
	if !ok {
		t.Error("ExistsByUsername 应返回 true")
	}

	ok, _ = repo.ExistsByEmail(ctx, "exists@example.com")
	if !ok {
		t.Error("ExistsByEmail 应返回 true")
	}

	ok, _ = repo.ExistsByPhone(ctx, "13900139000")
	if !ok {
		t.Error("ExistsByPhone 应返回 true")
	}

	ok, _ = repo.ExistsByUsername(ctx, "notexist")
	if ok {
		t.Error("不存在的用户 ExistsByUsername 应返回 false")
	}
}

// TestUserRepository_List 测试列表查询
func TestUserRepository_List(t *testing.T) {
	db := setupTestDB(t)
	repo := NewUserRepository(db)
	ctx := context.Background()

	for i := 0; i < 5; i++ {
		repo.Create(ctx, &domain.User{
			Username: "listuser" + string(rune('0'+i)),
			Password: "hash",
			Status:   domain.UserStatusActive,
		})
	}

	users, total, err := repo.List(ctx, 0, 10)
	if err != nil {
		t.Fatalf("List() error = %v", err)
	}
	if len(users) != 5 {
		t.Errorf("len(users) = %d, want 5", len(users))
	}
	if total != 5 {
		t.Errorf("total = %d, want 5", total)
	}
}

// TestUserRepository_GetByPhone tests phone lookup
func TestUserRepository_GetByPhone(t *testing.T) {
	db := setupTestDB(t)
	repo := NewUserRepository(db)
	ctx := context.Background()

	user := &domain.User{
		Username: "phoneuser",
		Email:    domain.StrPtr("phone@example.com"),
		Phone:    domain.StrPtr("13700137000"),
		Password: "hash",
		Status:   domain.UserStatusActive,
	}
	repo.Create(ctx, user)

	found, err := repo.GetByPhone(ctx, "13700137000")
	if err != nil {
		t.Fatalf("GetByPhone() error = %v", err)
	}
	if found.Username != "phoneuser" {
		t.Errorf("Username = %v, want phoneuser", found.Username)
	}
}

// TestUserRepository_ListByStatus tests status filtering
func TestUserRepository_ListByStatus(t *testing.T) {
	db := setupTestDB(t)
	repo := NewUserRepository(db)
	ctx := context.Background()

	repo.Create(ctx, &domain.User{
		Username: "active1",
		Password: "hash",
		Status:   domain.UserStatusActive,
	})
	repo.Create(ctx, &domain.User{
		Username: "active2",
		Password: "hash",
		Status:   domain.UserStatusActive,
	})
	repo.Create(ctx, &domain.User{
		Username: "inactive1",
		Password: "hash",
		Status:   domain.UserStatusInactive,
	})

	users, total, err := repo.ListByStatus(ctx, domain.UserStatusActive, 0, 10)
	if err != nil {
		t.Fatalf("ListByStatus() error = %v", err)
	}
	if len(users) != 2 {
		t.Errorf("len(users) = %d, want 2", len(users))
	}
	if total != 2 {
		t.Errorf("total = %d, want 2", total)
	}
}

// TestUserRepository_UpdateStatus tests status update
func TestUserRepository_UpdateStatus(t *testing.T) {
	db := setupTestDB(t)
	repo := NewUserRepository(db)
	ctx := context.Background()

	user := &domain.User{
		Username: "statususer",
		Email:    domain.StrPtr("status@example.com"),
		Password: "hash",
		Status:   domain.UserStatusActive,
	}
	repo.Create(ctx, user)

	err := repo.UpdateStatus(ctx, user.ID, domain.UserStatusInactive)
	if err != nil {
		t.Fatalf("UpdateStatus() error = %v", err)
	}

	found, _ := repo.GetByID(ctx, user.ID)
	if found.Status != domain.UserStatusInactive {
		t.Errorf("Status = %v, want Inactive", found.Status)
	}
}

// TestUserRepository_BatchUpdateStatus tests batch status update
func TestUserRepository_BatchUpdateStatus(t *testing.T) {
	db := setupTestDB(t)
	repo := NewUserRepository(db)
	ctx := context.Background()

	user1 := &domain.User{
		Username: "batch1",
		Email:    domain.StrPtr("batch1@example.com"),
		Password: "hash",
		Status:   domain.UserStatusActive,
	}
	user2 := &domain.User{
		Username: "batch2",
		Email:    domain.StrPtr("batch2@example.com"),
		Password: "hash",
		Status:   domain.UserStatusActive,
	}
	repo.Create(ctx, user1)
	repo.Create(ctx, user2)

	err := repo.BatchUpdateStatus(ctx, []int64{user1.ID, user2.ID}, domain.UserStatusInactive)
	if err != nil {
		t.Fatalf("BatchUpdateStatus() error = %v", err)
	}

	found1, _ := repo.GetByID(ctx, user1.ID)
	found2, _ := repo.GetByID(ctx, user2.ID)
	if found1.Status != domain.UserStatusInactive || found2.Status != domain.UserStatusInactive {
		t.Error("BatchUpdateStatus failed")
	}
}

// TestUserRepository_BatchDelete tests batch delete
func TestUserRepository_BatchDelete(t *testing.T) {
	db := setupTestDB(t)
	repo := NewUserRepository(db)
	ctx := context.Background()

	user1 := &domain.User{
		Username: "del1",
		Email:    domain.StrPtr("del1@example.com"),
		Password: "hash",
		Status:   domain.UserStatusActive,
	}
	user2 := &domain.User{
		Username: "del2",
		Email:    domain.StrPtr("del2@example.com"),
		Password: "hash",
		Status:   domain.UserStatusActive,
	}
	repo.Create(ctx, user1)
	repo.Create(ctx, user2)

	err := repo.BatchDelete(ctx, []int64{user1.ID, user2.ID})
	if err != nil {
		t.Fatalf("BatchDelete() error = %v", err)
	}

	_, err1 := repo.GetByID(ctx, user1.ID)
	_, err2 := repo.GetByID(ctx, user2.ID)
	if err1 == nil || err2 == nil {
		t.Error("BatchDelete should have deleted users")
	}
}

// TestUserRepository_Search tests user search
func TestUserRepository_Search(t *testing.T) {
	db := setupTestDB(t)
	repo := NewUserRepository(db)
	ctx := context.Background()

	repo.Create(ctx, &domain.User{
		Username:  "searchuser1",
		Nickname: "张三",
		Email:    domain.StrPtr("zhangsan@example.com"),
		Password: "hash",
		Status:   domain.UserStatusActive,
	})
	repo.Create(ctx, &domain.User{
		Username:  "searchuser2",
		Nickname: "李四",
		Email:    domain.StrPtr("lisi@example.com"),
		Password: "hash",
		Status:   domain.UserStatusActive,
	})

	users, total, err := repo.Search(ctx, "zhang", 0, 10)
	if err != nil {
		t.Fatalf("Search() error = %v", err)
	}
	if len(users) != 1 {
		t.Errorf("len(users) = %d, want 1", len(users))
	}
	if total != 1 {
		t.Errorf("total = %d, want 1", total)
	}
}

// TestUserRepository_Search_LikePattern tests search with LIKE special chars
func TestUserRepository_Search_LikePattern(t *testing.T) {
	db := setupTestDB(t)
	repo := NewUserRepository(db)
	ctx := context.Background()

	repo.Create(ctx, &domain.User{
		Username:  "user%with%percent",
		Nickname: "测试用户",
		Email:    domain.StrPtr("percent@example.com"),
		Password: "hash",
		Status:   domain.UserStatusActive,
	})

	// Search should handle LIKE special chars safely
	users, _, err := repo.Search(ctx, "%", 0, 10)
	if err != nil {
		t.Fatalf("Search() error = %v", err)
	}
	// Should not error and should escape properly
	_ = users
}
