package repository

import (
	"context"
	"errors"
	"fmt"

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

	"backend/pkg/library/mongo"

	"time"

	"github.com/zeromicro/go-zero/core/stores/cache"
	"github.com/zeromicro/go-zero/core/stores/mon"
	"go.mongodb.org/mongo-driver/v2/bson"
	mongodriver "go.mongodb.org/mongo-driver/v2/mongo"
	"go.mongodb.org/mongo-driver/v2/mongo/options"
)

type UserRepositoryParam struct {
	Conf      *mongo.Conf
	CacheConf cache.CacheConf
	DBOpts    []mon.Option
	CacheOpts []cache.Option
}

type UserRepository struct {
	DB mongo.DocumentDBWithCacheUseCase
}

func NewUserRepository(param UserRepositoryParam) repository.UserRepository {
	e := entity.User{}
	documentDB, err := mongo.MustDocumentDBWithCache(
		param.Conf,
		e.CollectionName(),
		param.CacheConf,
		param.DBOpts,
		param.CacheOpts,
	)
	if err != nil {
		panic(err)
	}

	return &UserRepository{
		DB: documentDB,
	}
}

func (repo *UserRepository) Insert(ctx context.Context, data *entity.User) error {
	if data.ID.IsZero() {
		now := time.Now().UTC().UnixNano()
		data.ID = bson.NewObjectID()
		data.CreateAt = &now
		data.UpdateAt = &now
	}

	rk := domain.GetUserRedisKey(data.ID.Hex())
	_, err := repo.DB.InsertOne(ctx, rk, data)

	return err
}

func (repo *UserRepository) FindOne(ctx context.Context, id string) (*entity.User, error) {
	oid, err := bson.ObjectIDFromHex(id)
	if err != nil {
		return nil, ErrInvalidObjectID
	}

	var data entity.User
	rk := domain.GetUserRedisKey(id)
	err = repo.DB.FindOne(ctx, rk, &data, bson.M{"_id": oid})
	switch {
	case err == nil:
		return &data, nil
	case errors.Is(err, mon.ErrNotFound):
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (repo *UserRepository) Update(ctx context.Context, data *entity.User) (*mongodriver.UpdateResult, error) {
	now := time.Now().UTC().UnixNano()
	data.UpdateAt = &now

	rk := domain.GetUserRedisKey(data.ID.Hex())
	res, err := repo.DB.UpdateOne(ctx, rk, bson.M{"_id": data.ID}, bson.M{"$set": data})

	return res, err
}

func (repo *UserRepository) Delete(ctx context.Context, id string) (int64, error) {
	oid, err := bson.ObjectIDFromHex(id)
	if err != nil {
		return 0, ErrInvalidObjectID
	}

	rk := domain.GetUserRedisKey(id)
	res, err := repo.DB.DeleteOne(ctx, rk, bson.M{"_id": oid})

	return res, err
}

func (repo *UserRepository) UpdateUserDetailsByUID(ctx context.Context, data *repository.UpdateUserInfoRequest) error {
	updateFields := bson.M{}

	if data.AlarmCategory != nil {
		updateFields["alarm_category"] = *data.AlarmCategory
	}
	if data.UserStatus != nil {
		updateFields["user_status"] = *data.UserStatus
	}
	if data.PreferredLanguage != nil {
		updateFields["preferred_language"] = *data.PreferredLanguage
	}
	if data.Currency != nil {
		updateFields["currency"] = *data.Currency
	}
	if data.Nickname != nil {
		updateFields["nickname"] = *data.Nickname
	}
	if data.AvatarURL != nil {
		updateFields["avatar_url"] = *data.AvatarURL
	}
	if data.FullName != nil {
		updateFields["full_name"] = *data.FullName
	}
	if data.GenderCode != nil {
		updateFields["gender_code"] = *data.GenderCode
	}
	if data.Birthdate != nil {
		updateFields["birthdate"] = *data.Birthdate
	}
	if data.Address != nil {
		updateFields["address"] = *data.Address
	}

	if len(updateFields) == 0 {
		return nil
	}

	updateFields["update_at"] = time.Now().UTC().UnixNano()

	filter := bson.M{"uid": data.UID}
	update := bson.M{"$set": updateFields}

	// 不常寫，再找一次可接受
	id := repo.UIDToID(ctx, data.UID)
	if id == "" {
		return errors.New("invalid uid")
	}
	rk := domain.GetUserRedisKey(id)
	result, err := repo.DB.UpdateOne(ctx, rk, filter, update, &options.UpdateOneOptions{Upsert: &[]bool{false}[0]})
	if err != nil {
		return err
	}

	if result.MatchedCount == 0 {
		return ErrNotFound // 自定義的錯誤表示未找到記錄
	}

	return nil
}

func (repo *UserRepository) UpdateStatus(ctx context.Context, uid string, status int32) error {
	filter := bson.M{"uid": uid}

	// 構建更新內容，僅更新 status 字段並記錄 update_at 時間
	update := bson.M{
		"$set": bson.M{
			"user_status": status,
			"update_at":   time.Now().UTC().UnixNano(),
		},
	}

	// 不常寫，再找一次可接受
	id := repo.UIDToID(ctx, uid)
	if id == "" {
		return errors.New("invalid uid")
	}
	rk := domain.GetUserRedisKey(id)

	// 執行更新操作
	result, err := repo.DB.UpdateOne(ctx, rk, filter, update, &options.UpdateOneOptions{Upsert: &[]bool{false}[0]})
	if err != nil {
		return fmt.Errorf("failed to update status for uid %s: %w", uid, err)
	}

	// 檢查更新結果，若沒有匹配的文檔，則返回錯誤
	if result.MatchedCount == 0 {
		return ErrNotFound // 自定義的錯誤表示未找到記錄
	}

	return nil
}

func (repo *UserRepository) FindOneByUID(ctx context.Context, uid string) (*entity.User, error) {
	// 構建查找條件
	filter := bson.M{"uid": uid}
	var data entity.User

	// 不常寫，再找一次可接受
	id := repo.UIDToID(ctx, uid)
	if id == "" {
		return nil, ErrNotFound
	}
	rk := domain.GetUserRedisKey(id)

	err := repo.DB.FindOne(ctx, rk, &data, filter)
	switch {
	case err == nil:
		return &data, nil
	case errors.Is(err, mon.ErrNotFound):
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (repo *UserRepository) FindOneByNickName(ctx context.Context, nickName string) (*entity.User, error) {
	// 構建查找條件
	filter := bson.M{"nickname": nickName}
	var data entity.User

	err := repo.DB.GetClient().FindOne(ctx, &data, filter)
	switch {
	case err == nil:
		return &data, nil
	case errors.Is(err, mon.ErrNotFound):
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (repo *UserRepository) ListMembers(ctx context.Context, params *repository.UserQueryParams) ([]*entity.User, int64, error) {
	// 構建查詢條件
	filter := bson.M{}

	if params.AlarmCategory != nil {
		filter["alarm_category"] = *params.AlarmCategory
	}
	if params.UserStatus != nil {
		filter["user_status"] = *params.UserStatus
	}
	if params.CreateStartTime != nil || params.CreateEndTime != nil {
		timeFilter := bson.M{}
		if params.CreateStartTime != nil {
			timeFilter["$gte"] = *params.CreateStartTime
		}
		if params.CreateEndTime != nil {
			timeFilter["$lte"] = *params.CreateEndTime
		}
		filter["create_at"] = timeFilter
	}

	// 計算符合條件的總數
	count, err := repo.DB.GetClient().CountDocuments(ctx, filter)
	if err != nil {
		return nil, 0, err
	}

	// 構建查詢選項（分頁）
	opts := options.Find().
		SetSkip(params.PageSize * (params.PageIndex - 1)).
		SetLimit(params.PageSize)

	// 執行查詢
	var users = make([]*entity.User, 0, params.PageSize)
	err = repo.DB.GetClient().Find(ctx, &users, filter, opts)
	if err != nil {
		return nil, 0, err
	}

	return users, count, nil
}

func (repo *UserRepository) UpdateEmailVerifyStatus(ctx context.Context, uid, email string) error {
	// 構建查找條件
	filter := bson.M{"uid": uid}

	// 構建更新內容，僅更新 status 字段並記錄 update_at 時間
	update := bson.M{
		"$set": bson.M{
			"email":     email,
			"update_at": time.Now().UTC().UnixNano(),
		},
	}

	// 不常寫，再找一次可接受
	id := repo.UIDToID(ctx, uid)
	if id == "" {
		return fmt.Errorf("invalid uid")
	}
	rk := domain.GetUserRedisKey(id)

	// 執行更新操作
	result, err := repo.DB.UpdateOne(ctx, rk, filter, update, &options.UpdateOneOptions{Upsert: &[]bool{false}[0]})
	if err != nil {
		return fmt.Errorf("failed to update status for uid %s: %w", uid, err)
	}

	// 檢查更新結果，若沒有匹配的文檔，則返回錯誤
	if result.MatchedCount == 0 {
		return ErrNotFound // 自定義的錯誤表示未找到記錄
	}

	return nil
}

func (repo *UserRepository) UpdatePhoneVerifyStatus(ctx context.Context, uid, phone string) error {
	// 構建查找條件
	filter := bson.M{"uid": uid}

	// 構建更新內容，僅更新 status 字段並記錄 update_at 時間
	update := bson.M{
		"$set": bson.M{
			"phone_number": phone,
			"update_at":    time.Now().UTC().UnixNano(),
		},
	}

	// 不常寫，再找一次可接受
	id := repo.UIDToID(ctx, uid)
	if id == "" {
		return fmt.Errorf("invalid uid")
	}
	rk := domain.GetUserRedisKey(id)

	// 執行更新操作
	result, err := repo.DB.UpdateOne(ctx, rk, filter, update, &options.UpdateOneOptions{Upsert: &[]bool{false}[0]})
	if err != nil {
		return err
	}

	// 檢查更新結果，若沒有匹配的文檔，則返回錯誤
	if result.MatchedCount == 0 {
		return ErrNotFound // 自定義的錯誤表示未找到記錄
	}

	return nil
}

func (repo *UserRepository) UIDToID(ctx context.Context, uid string) string {
	filter := bson.M{"uid": uid}
	var user entity.User
	opts := options.FindOne().SetProjection(bson.M{
		"_id": 1,
	})
	err := repo.DB.GetClient().FindOne(ctx, &user, filter, opts)
	if err != nil {
		return ""
	}

	return user.ID.Hex()
}

func (repo *UserRepository) Index20241226001UP(ctx context.Context) (*mongodriver.Cursor, error) {
	// db.user_info.createIndex({"uid": 1},{unique: true})
	repo.DB.PopulateIndex(ctx, "uid", 1, true)
	// db.user_info.createIndex({"create_at": 1})
	repo.DB.PopulateIndex(ctx, "create_at", 1, false)

	return repo.DB.GetClient().Indexes().List(ctx)
}
