package repository

import (
	"context"
	"errors"
	"fmt"
	"testing"
	"time"

	"backend/pkg/member/domain/entity"
	"backend/pkg/member/domain/member"
	"backend/pkg/member/domain/repository"

	mgo "backend/pkg/library/mongo"

	"github.com/alicebob/miniredis/v2"
	"github.com/stretchr/testify/assert"
	"github.com/zeromicro/go-zero/core/stores/cache"
	"github.com/zeromicro/go-zero/core/stores/redis"
	"go.mongodb.org/mongo-driver/v2/bson"
	"google.golang.org/protobuf/proto"
)

func SetupTestUserRepository(db string) (repository.UserRepository, func(), error) {
	h, p, tearDown, err := startMongoContainer()
	if err != nil {
		return nil, nil, err
	}
	s, _ := miniredis.Run()

	conf := &mgo.Conf{
		Schema:                           Schema,
		Host:                             fmt.Sprintf("%s:%s", h, p),
		Database:                         db,
		MaxStaleness:                     300,
		MaxPoolSize:                      100,
		MinPoolSize:                      100,
		MaxConnIdleTime:                  300,
		Compressors:                      []string{},
		EnableStandardReadWriteSplitMode: false,
		ConnectTimeoutMs:                 3000,
	}

	cacheConf := cache.CacheConf{
		cache.NodeConf{
			RedisConf: redis.RedisConf{
				Host: s.Addr(),
				Type: redis.NodeType,
			},
			Weight: 100,
		},
	}

	cacheOpts := []cache.Option{
		cache.WithExpiry(1000 * time.Microsecond),
		cache.WithNotFoundExpiry(1000 * time.Microsecond),
	}

	param := UserRepositoryParam{
		Conf:      conf,
		CacheConf: cacheConf,
		CacheOpts: cacheOpts,
	}

	repo := NewUserRepository(param)
	_, _ = repo.Index20241226001UP(context.Background())

	return repo, tearDown, nil
}

func TestCustomUserModel_UpdateEmailVerifyStatus(t *testing.T) {
	repo, tearDown, err := SetupTestUserRepository("testDB")
	defer tearDown()
	assert.NoError(t, err)

	initialUser := &entity.User{
		UID:   "test_uid_email",
		Email: proto.String("old_email@example.com"),
	}
	err = repo.Insert(context.Background(), initialUser)
	assert.NoError(t, err)

	err = repo.UpdateEmailVerifyStatus(context.Background(), "test_uid_email", "new_email@example.com")
	assert.NoError(t, err)

	updatedUser, err := repo.FindOneByUID(context.Background(), "test_uid_email")
	assert.NoError(t, err)
	assert.Equal(t, "new_email@example.com", *updatedUser.Email)
}

func TestCustomUserModel_UpdatePhoneVerifyStatus(t *testing.T) {
	repo, tearDown, err := SetupTestUserRepository("testDB")
	defer tearDown()
	assert.NoError(t, err)

	initialUser := &entity.User{
		UID:         "test_uid_phone",
		PhoneNumber: proto.String("123456789"),
	}
	err = repo.Insert(context.Background(), initialUser)
	assert.NoError(t, err)

	err = repo.UpdatePhoneVerifyStatus(context.Background(), "test_uid_phone", "987654321")
	assert.NoError(t, err)

	updatedUser, err := repo.FindOneByUID(context.Background(), "test_uid_phone")
	assert.NoError(t, err)
	assert.Equal(t, "987654321", *updatedUser.PhoneNumber)
}

func TestCustomUserModel_UpdateUserDetailsByUID(t *testing.T) {
	repo, tearDown, err := SetupTestUserRepository("testDB")
	defer tearDown()
	assert.NoError(t, err)

	tests := []struct {
		name        string
		initialData *entity.User
		updateData  *repository.UpdateUserInfoRequest
		expectError bool
	}{
		{
			name: "Update user details by UID",
			initialData: &entity.User{
				UID:      "test_uid_1",
				Nickname: proto.String("old_nickname"),
				FullName: proto.String("Old Full Name"),
			},
			updateData: &repository.UpdateUserInfoRequest{
				UID:      "test_uid_1",
				Nickname: proto.String("new_nickname"),
				FullName: proto.String("New Full Name"),
			},
			expectError: false,
		},
		{
			name:        "User not found",
			initialData: nil,
			updateData: &repository.UpdateUserInfoRequest{
				UID:      "non_existent_uid",
				Nickname: proto.String("nickname"),
			},
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.initialData != nil {
				err := repo.Insert(context.Background(), tt.initialData)
				assert.NoError(t, err)
			}

			err := repo.UpdateUserDetailsByUID(context.Background(), tt.updateData)
			if tt.expectError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)

				updatedUser, err := repo.FindOneByUID(context.Background(), tt.updateData.UID)
				assert.NoError(t, err)
				assert.Equal(t, *tt.updateData.Nickname, *updatedUser.Nickname)
				assert.Equal(t, *tt.updateData.FullName, *updatedUser.FullName)
			}
		})
	}
}

func TestCustomUserModel_FindOneByUID(t *testing.T) {
	repo, tearDown, err := SetupTestUserRepository("testDB")
	defer tearDown()
	assert.NoError(t, err)

	initialUser := &entity.User{
		UID: "test_uid",
	}

	err = repo.Insert(context.Background(), initialUser)
	assert.NoError(t, err)

	user, err := repo.FindOneByUID(context.Background(), "test_uid")
	assert.NoError(t, err)
	assert.Equal(t, "test_uid", user.UID)
}

func TestCustomUserModel_ListMembers(t *testing.T) {
	repo, tearDown, err := SetupTestUserRepository("testDB")
	defer tearDown()
	assert.NoError(t, err)

	users := []*entity.User{
		{UID: "uid1", UserStatus: member.AccountStatusActive},
		{UID: "uid2", UserStatus: member.AccountStatusActive},
		{UID: "uid3", UserStatus: member.AccountStatusUnverified},
	}

	for _, user := range users {
		err := repo.Insert(context.Background(), user)
		assert.NoError(t, err)
	}

	activeStatus := member.AccountStatusActive
	params := &repository.UserQueryParams{
		UserStatus: &activeStatus,
		PageSize:   2,
		PageIndex:  1,
	}

	result, count, err := repo.ListMembers(context.Background(), params)
	assert.NoError(t, err)
	assert.Equal(t, int64(2), count)
	assert.Len(t, result, 2)
}

func TestCustomUserModel_FindOne(t *testing.T) {
	repo, tearDown, err := SetupTestUserRepository("testDB")
	defer tearDown()
	assert.NoError(t, err)
	// 準備測試資料
	initialUser := &entity.User{
		UID: "test_uid",
	}

	err = repo.Insert(context.Background(), initialUser)
	assert.NoError(t, err)

	tests := []struct {
		name        string
		id          string
		expectError bool
		expectedUID string
	}{
		{
			name:        "Valid FindOne",
			id:          initialUser.ID.Hex(),
			expectError: false,
			expectedUID: initialUser.UID,
		},
		{
			name:        "Invalid ObjectID",
			id:          "invalid_id",
			expectError: true,
		},
		{
			name:        "Non-existent ObjectID",
			id:          bson.NewObjectID().Hex(),
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := repo.FindOne(context.Background(), tt.id)
			if tt.expectError {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.expectedUID, result.UID, "找到的 UID 應符合預期")
			}
		})
	}
}

func TestCustomUserModel_Update(t *testing.T) {
	repo, tearDown, err := SetupTestUserRepository("testDB")
	defer tearDown()
	assert.NoError(t, err)
	// 準備測試資料
	initialUser := &entity.User{
		UID: "test_uid",
	}

	err = repo.Insert(context.Background(), initialUser)
	assert.NoError(t, err)

	updatedUID := "test_uid"
	initialUser.UID = updatedUID

	// 執行更新操作
	_, err = repo.Update(context.Background(), initialUser)
	assert.NoError(t, err, "應成功更新資料")

	// 確認更新結果
	updatedAccountUid, err := repo.FindOne(context.Background(), initialUser.ID.Hex())
	assert.NoError(t, err, "應能找到更新後的資料")
	assert.Equal(t, updatedUID, updatedAccountUid.UID, "更新後的 UID 應符合預期")
}

func TestCustomUserModel_Delete(t *testing.T) {
	repo, tearDown, err := SetupTestUserRepository("testDB")
	defer tearDown()
	assert.NoError(t, err)
	// 準備測試資料
	initialUser := &entity.User{
		UID: "test_uid",
	}

	err = repo.Insert(context.Background(), initialUser)
	assert.NoError(t, err)
	// 執行刪除操作
	count, err := repo.Delete(context.Background(), initialUser.ID.Hex())
	assert.NoError(t, err, "應成功刪除資料")
	assert.Equal(t, int64(1), count, "刪除數量應為 1")

	// 確認資料已被刪除
	_, err = repo.FindOne(context.Background(), initialUser.ID.Hex())
	assert.Error(t, err, "應無法找到已刪除的資料")
	assert.Equal(t, ErrNotFound, err, "應返回 ErrNotFound 錯誤")
}

func TestUserRepository_UpdateStatus(t *testing.T) {
	repo, tearDown, err := SetupTestUserRepository("testDB")
	defer tearDown()
	assert.NoError(t, err)
	// 插入測試數據
	e := &entity.User{
		UID:        "test_uid",
		UserStatus: member.AccountStatusActive,
	}
	err = repo.Insert(context.TODO(), e)
	assert.NoError(t, err)

	// 測試用例
	tests := []struct {
		name        string
		uid         string
		status      member.Status
		expectError bool
		expectedErr error
	}{
		{
			name:        "Valid UID - Successful update",
			uid:         e.UID,
			status:      member.AccountStatusActive,
			expectError: false,
		},
		{
			name:        "Invalid UID - No mapping to ID",
			uid:         "invalid_uid",
			status:      3,
			expectError: true,
			expectedErr: errors.New("invalid uid"),
		},
		{
			name:        "Non-existent UID",
			uid:         "non_existent_uid",
			status:      4,
			expectError: true,
			expectedErr: errors.New("invalid uid"),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := repo.UpdateStatus(context.TODO(), tt.uid, tt.status.ToInt32())
			if tt.expectError {
				assert.Error(t, err)
				assert.Equal(t, tt.expectedErr.Error(), err.Error())
			} else {
				assert.NoError(t, err)

				// 驗證數據是否正確更新
				user, err := repo.FindOneByUID(context.TODO(), tt.uid)
				assert.NoError(t, err)
				assert.Equal(t, tt.status, user.UserStatus, "UserStatus 應更新為預期值")
			}
		})
	}
}

func TestCustomUserModel_FindOneByNickName(t *testing.T) {
	repo, tearDown, err := SetupTestUserRepository("testDB")
	defer tearDown()
	assert.NoError(t, err)

	initialUser := &entity.User{
		UID:      "test_uid",
		Nickname: proto.String("my_name"),
	}

	err = repo.Insert(context.Background(), initialUser)
	assert.NoError(t, err)

	user, err := repo.FindOneByNickName(context.Background(), "my_name")

	assert.NoError(t, err)
	assert.Equal(t, "test_uid", user.UID)
}
