package repository

import (
	"context"
	"errors"
	"time"

	"backend/pkg/member/domain/member"

	"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 AccountRepositoryParam struct {
	Conf      *mongo.Conf
	CacheConf cache.CacheConf
	DBOpts    []mon.Option
	CacheOpts []cache.Option
}

type AccountRepository struct {
	DB mongo.DocumentDBWithCacheUseCase
}

func NewAccountRepository(param AccountRepositoryParam) repository.AccountRepository {
	e := entity.Account{}
	documentDB, err := mongo.MustDocumentDBWithCache(
		param.Conf,
		e.CollectionName(),
		param.CacheConf,
		param.DBOpts,
		param.CacheOpts,
	)
	if err != nil {
		panic(err)
	}

	return &AccountRepository{
		DB: documentDB,
	}
}

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

	return err
}

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

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

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

	return res, err
}

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

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

func (repo *AccountRepository) FindOneByAccount(ctx context.Context, loginID string) (*entity.Account, error) {
	// todo: 之後需要同步快取
	var data entity.Account
	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 *AccountRepository) UpdateTokenByLoginID(ctx context.Context, account string, token string, platform member.Platform) error {
	// todo: 之後需要同步快取
	filter := bson.M{"login_id": account, "platform": platform.ToInt64()}
	update := bson.M{
		"$set": bson.M{
			"token":     token,
			"update_at": time.Now().UTC().UnixNano(),
		},
	}
	var data entity.Account
	err := repo.DB.GetClient().FindOne(ctx, &data, filter)
	if err != nil {
		return err
	}

	rk := domain.GetAccountRedisKey(data.ID.Hex())

	modify, err := repo.DB.UpdateOne(ctx, rk, bson.M{"_id": data.ID}, update)
	if err != nil {
		return err
	}

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

	return nil
}

func (repo *AccountRepository) Index20241226001UP(ctx context.Context) (*mongodriver.Cursor, error) {
	// 等價於 db.account.createIndex({ "login_id": 1, "platform": 1}, {unique: true})
	repo.DB.PopulateMultiIndex(ctx, []string{
		"login_id",
		"platform",
	}, []int32{1, 1}, true)

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

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