package repository

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

	"backend/pkg/member/domain/entity"
	"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"
)

func SetupTestAccountUIDRepository(db string) (repository.AccountUIDRepository, 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 := AccountUIDRepositoryParam{
		Conf:      conf,
		CacheConf: cacheConf,
		CacheOpts: cacheOpts,
	}

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

	return repo, tearDown, nil
}

func TestDefaultAccountUidModel_Insert(t *testing.T) {
	repo, tearDown, err := SetupTestAccountUIDRepository("testDB")
	defer tearDown()
	assert.NoError(t, err)
	tests := []struct {
		name        string
		accountUid  *entity.AccountUID
		expectError bool
	}{
		{
			name: "Valid AccountUid insert",
			accountUid: &entity.AccountUID{
				LoginID: "testlogin1",
				UID:     "testuid1",
				Type:    1,
			},
			expectError: false,
		},
		{
			name: "Insert with missing UID",
			accountUid: &entity.AccountUID{
				LoginID: "testlogin2",
				Type:    2,
			},
			expectError: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// 執行插入測試帳戶 UID
			err := repo.Insert(context.Background(), tt.accountUid)
			if tt.expectError {
				assert.Error(t, err, "插入操作應該失敗")
			} else {
				assert.NoError(t, err, "插入操作應該成功")

				// 驗證 ObjectID 和時間戳是否生成
				assert.NotZero(t, tt.accountUid.ID, "應生成 ID")
				assert.NotNil(t, tt.accountUid.CreateAt, "CreateAt 應被設置")
				assert.NotNil(t, tt.accountUid.UpdateAt, "UpdateAt 應被設置")

				// 驗證插入的時間是否合理
				now := time.Now().UTC().UnixNano()
				assert.LessOrEqual(t, *tt.accountUid.CreateAt, now, "CreateAt 應在當前時間之前")
				assert.LessOrEqual(t, *tt.accountUid.UpdateAt, now, "UpdateAt 應在當前時間之前")

				// 驗證插入的資料是否正確
				insertedAccountUid, err := repo.FindOne(context.Background(), tt.accountUid.ID.Hex())
				assert.NoError(t, err, "應該可以找到插入的帳號 UID 資料")
				assert.Equal(t, tt.accountUid.LoginID, insertedAccountUid.LoginID, "LoginID 應相同")
				assert.Equal(t, tt.accountUid.UID, insertedAccountUid.UID, "UID 應相同")
				assert.Equal(t, tt.accountUid.Type, insertedAccountUid.Type, "Type 應相同")
			}
		})
	}
}

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

	// 準備測試資料
	accountUid := &entity.AccountUID{
		LoginID: "testlogin",
		UID:     "testuid",
		Type:    1,
	}
	err = repo.Insert(context.Background(), accountUid)
	assert.NoError(t, err, "應成功插入測試資料")

	tests := []struct {
		name        string
		id          string
		expectError bool
		expectedUID string
	}{
		{
			name:        "Valid FindOne",
			id:          accountUid.ID.Hex(),
			expectError: false,
			expectedUID: accountUid.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 TestDefaultAccountUidModel_Update(t *testing.T) {
	repo, tearDown, err := SetupTestAccountUIDRepository("testDB")
	defer tearDown()
	assert.NoError(t, err)

	// 準備測試資料
	accountUid := &entity.AccountUID{
		LoginID: "testlogin",
		UID:     "testuid",
		Type:    1,
	}
	err = repo.Insert(context.Background(), accountUid)
	assert.NoError(t, err)

	updatedUID := "updatedUID"
	accountUid.UID = updatedUID

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

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

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

	// 準備測試資料
	accountUid := &entity.AccountUID{
		LoginID: "testlogin",
		UID:     "testuid",
		Type:    1,
	}
	err = repo.Insert(context.Background(), accountUid)
	assert.NoError(t, err)

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

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

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

	// 準備測試資料
	accountUid := &entity.AccountUID{
		LoginID: "testloginid",
		UID:     "testuid",
		Type:    1,
	}
	err = repo.Insert(context.Background(), accountUid)
	assert.NoError(t, err, "應成功插入測試資料")

	tests := []struct {
		name        string
		loginID     string
		expectError bool
		expectedUID string
	}{
		{
			name:        "Valid FindUIDByLoginID",
			loginID:     "testloginid",
			expectError: false,
			expectedUID: "testuid",
		},
		{
			name:        "Non-existent LoginID",
			loginID:     "nonexistent",
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := repo.FindUIDByLoginID(context.Background(), tt.loginID)
			if tt.expectError {
				assert.Error(t, err)
				assert.Equal(t, ErrNotFound, err, "應返回 ErrNotFound 錯誤")
			} else {
				assert.NoError(t, err, "應成功找到符合的記錄")
				assert.Equal(t, tt.expectedUID, result.UID, "找到的 UID 應符合預期")
			}
		})
	}
}
