package repository

import (
	"context"
	"errors"
	"time"

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

	"backend/pkg/library/mongo"

	"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"
)

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

type AccountUIDRepository struct {
	DB mongo.DocumentDBWithCacheUseCase
}

func NewAccountUIDRepository(param AccountUIDRepositoryParam) repository.AccountUIDRepository {
	e := entity.AccountUID{}
	documentDB, err := mongo.MustDocumentDBWithCache(
		param.Conf,
		e.CollectionName(),
		param.CacheConf,
		param.DBOpts,
		param.CacheOpts,
	)
	if err != nil {
		panic(err)
	}

	return &AccountUIDRepository{
		DB: documentDB,
	}
}

func (repo *AccountUIDRepository) Insert(ctx context.Context, data *entity.AccountUID) error {
	if data.ID.IsZero() {
		now := time.Now().UTC().UnixNano()
		data.ID = bson.NewObjectID()
		data.CreateAt = &now
		data.UpdateAt = &now
	}
	rk := domain.GetAccountUIDRedisKey(data.ID.Hex())
	_, err := repo.DB.InsertOne(ctx, rk, data)

	return err
}

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

	var data entity.AccountUID
	rk := domain.GetAccountUIDRedisKey(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 *AccountUIDRepository) Update(ctx context.Context, data *entity.AccountUID) (*mongodriver.UpdateResult, error) {
	now := time.Now().UTC().UnixNano()
	data.UpdateAt = &now

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

	return res, err
}

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

	return repo.DB.DeleteOne(ctx, rk, bson.M{"_id": oid})
}

func (repo *AccountUIDRepository) FindUIDByLoginID(ctx context.Context, loginID string) (*entity.AccountUID, error) {
	var data entity.AccountUID

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

func (repo *AccountUIDRepository) FindOneByUID(ctx context.Context, uid string) (*entity.AccountUID, error) {
	var data entity.AccountUID

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

func (repo *AccountUIDRepository) Index20241226001UP(ctx context.Context) (*mongodriver.Cursor, error) {
	// 等價於 db.account_uid_binding.createIndex({"login_id": 1}, {unique: true})
	repo.DB.PopulateIndex(ctx, "login_id", 1, true)

	// 等價於 db.account_uid_binding.createIndex({"uid": 1})
	repo.DB.PopulateIndex(ctx, "uid", 1, false)

	// 等價於 db.account_uid_binding.createIndex({"create_at": 1})
	repo.DB.PopulateIndex(ctx, "create_at", 1, false)

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