package repository

import (
	"backend/pkg/member/domain/member"
	"context"
	"errors"
	"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"
	"go.mongodb.org/mongo-driver/v2/bson"

	"github.com/zeromicro/go-zero/core/stores/redis"
)

func SetupTestAccountRepository(db string) (repository.AccountRepository, 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 := AccountRepositoryParam{
		Conf:      conf,
		CacheConf: cacheConf,
		CacheOpts: cacheOpts,
	}
	repo := NewAccountRepository(param)
	_, _ = repo.Index20241226001UP(context.Background())

	return repo, tearDown, nil
}

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

	tests := []struct {
		name        string
		account     *entity.Account
		expectError bool
	}{
		{
			name: "Valid account insert",
			account: &entity.Account{
				LoginID:  "testuser1",
				Token:    "testtoken1",
				Platform: 1,
			},
			expectError: false,
		},
	}

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

				// 檢查是否生成了 ObjectID 和時間戳
				assert.NotZero(t, tt.account.ID, "ID 應該被生成")
				assert.NotNil(t, tt.account.CreateAt, "CreateAt 應該被設置")
				assert.NotNil(t, tt.account.UpdateAt, "UpdateAt 應該被設置")

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

				// 確認插入的資料
				insertedAccount, err := repo.FindOne(context.Background(), tt.account.ID.Hex())
				assert.NoError(t, err, "應該可以找到插入的帳號資料")
				assert.Equal(t, tt.account.LoginID, insertedAccount.LoginID, "LoginID 應相同")
				assert.Equal(t, tt.account.Token, insertedAccount.Token, "Token 應相同")
				assert.Equal(t, tt.account.Platform, insertedAccount.Platform, "Platform 應相同")
			}
		})
	}
}

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

	// 插入測試帳戶
	account := &entity.Account{
		LoginID:  "testuser1",
		Token:    "testtoken1",
		Platform: 1,
	}
	err = repo.Insert(context.TODO(), account)
	assert.NoError(t, err, "插入應成功")
	nid := bson.NewObjectID()
	t.Logf("Inserted account ID: %s, nid:%s", account.ID.Hex(), nid.Hex())

	tests := []struct {
		name        string
		id          string
		expectError bool
		expectedErr error
	}{
		{
			name:        "Valid ID",
			id:          account.ID.Hex(),
			expectError: false,
		},
		{
			name:        "Non-existent ID",
			id:          nid.Hex(),
			expectError: true,
			expectedErr: ErrNotFound,
		},
		{
			name:        "Invalid ID format",
			id:          "invalid-id",
			expectError: true,
			expectedErr: ErrInvalidObjectID,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := repo.FindOne(context.TODO(), tt.id)
			if tt.expectError {
				assert.Error(t, err)
				assert.Equal(t, tt.expectedErr, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, account.LoginID, result.LoginID)
				assert.Equal(t, account.Token, result.Token)
			}
		})
	}
}

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

	// 插入測試帳戶
	account := &entity.Account{
		LoginID:  "testuser1",
		Token:    "testtoken1",
		Platform: 1,
	}
	err = repo.Insert(context.Background(), account)
	assert.NoError(t, err, "插入應成功")

	// 更新測試
	newToken := "updatedToken"
	account.Token = newToken

	_, err = repo.Update(context.Background(), account)
	assert.NoError(t, err, "更新應成功")

	// 驗證更新結果
	updatedAccount, err := repo.FindOne(context.Background(), account.ID.Hex())
	assert.NoError(t, err)
	assert.Equal(t, newToken, updatedAccount.Token, "Token 應更新")
}

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

	// 插入測試帳戶
	account := &entity.Account{
		LoginID:  "testuser1",
		Token:    "testtoken1",
		Platform: 1,
	}
	err = repo.Insert(context.Background(), account)
	assert.NoError(t, err, "插入應成功")

	tests := []struct {
		name        string
		id          string
		expectError bool
		expectedErr error
	}{
		{
			name:        "Valid delete",
			id:          account.ID.Hex(),
			expectError: false,
		},
		{
			name:        "Invalid ID format",
			id:          "invalid-id",
			expectError: true,
			expectedErr: ErrInvalidObjectID,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			deletedCount, err := repo.Delete(context.Background(), tt.id)
			if tt.expectError {
				assert.Error(t, err)
				assert.Equal(t, tt.expectedErr, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, int64(1), deletedCount, "刪除應成功")
			}
		})
	}
}

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

	// 插入測試帳戶
	account := &entity.Account{
		LoginID:  "testuser1",
		Token:    "testtoken1",
		Platform: 1,
	}
	err = repo.Insert(context.Background(), account)
	assert.NoError(t, err, "插入應成功")

	tests := []struct {
		name        string
		loginID     string
		expectedErr error
		expectFound bool
	}{
		{
			name:        "Valid Account Found",
			loginID:     "testuser1",
			expectedErr: nil,
			expectFound: true,
		},
		{
			name:        "Account Not Found",
			loginID:     "nonexistentuser",
			expectedErr: ErrNotFound,
			expectFound: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := repo.FindOneByAccount(context.Background(), tt.loginID)
			if tt.expectFound {
				assert.NoError(t, err)
				assert.Equal(t, account.LoginID, result.LoginID)
				assert.Equal(t, account.Token, result.Token)
			} else {
				assert.Error(t, err)
				assert.True(t, errors.Is(err, tt.expectedErr))
			}
		})
	}
}

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

	// 插入測試帳戶
	account := &entity.Account{
		LoginID:  "testuser2",
		Token:    "initialtoken",
		Platform: 1,
	}
	err = repo.Insert(context.Background(), account)
	assert.NoError(t, err, "插入應成功")

	tests := []struct {
		name        string
		loginID     string
		newToken    string
		platform    int64
		expectedErr error
		expectFound bool
	}{
		{
			name:        "Valid Update Token",
			loginID:     "testuser2",
			newToken:    "newtoken123",
			platform:    1,
			expectedErr: nil,
			expectFound: true,
		},
		{
			name:        "Account Not Found for Update",
			loginID:     "nonexistentuser",
			newToken:    "newtoken456",
			platform:    1,
			expectedErr: ErrNotFound,
			expectFound: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := repo.UpdateTokenByLoginID(context.Background(), tt.loginID, tt.newToken, member.Platform(tt.platform))
			if tt.expectFound {
				assert.NoError(t, err)

				// 驗證更新後的 token 值
				updatedAccount, findErr := repo.FindOneByAccount(context.Background(), tt.loginID)
				assert.NoError(t, findErr)
				assert.Equal(t, tt.newToken, updatedAccount.Token)
			} else {
				assert.Error(t, err)
				assert.True(t, errors.Is(err, tt.expectedErr))
			}
		})
	}
}
