package repository

import (
	"context"
	"errors"
	"time"

	libmongo "gateway/internal/library/mongo"
	authdomain "gateway/internal/model/auth/domain"
	"gateway/internal/model/auth/domain/entity"
	domrepo "gateway/internal/model/auth/domain/repository"

	"go.mongodb.org/mongo-driver/v2/bson"
	mongodriver "go.mongodb.org/mongo-driver/v2/mongo"
	"go.mongodb.org/mongo-driver/v2/mongo/options"
)

// InviteRepositoryParam configures the Mongo invite repository.
type InviteRepositoryParam struct {
	Conf *libmongo.Conf
}

type inviteRepository struct {
	db libmongo.DocumentDBUseCase
}

// NewInviteRepository creates a Mongo-backed InviteRepository.
func NewInviteRepository(param InviteRepositoryParam) domrepo.InviteRepository {
	documentDB, err := libmongo.NewDocumentDB(param.Conf, entity.InviteCode{}.CollectionName())
	if err != nil {
		panic(err)
	}
	return &inviteRepository{db: documentDB}
}

func (r *inviteRepository) GetByTenantAndCodeHash(ctx context.Context, tenantID, codeHash string) (*entity.InviteCode, error) {
	var doc entity.InviteCode
	filter := bson.M{
		authdomain.BSONFieldTenantID: tenantID,
		authdomain.BSONFieldCodeHash: codeHash,
	}
	if err := r.db.GetClient().FindOne(ctx, &doc, filter); err != nil {
		if errors.Is(err, mongodriver.ErrNoDocuments) {
			return nil, authdomain.ErrInviteNotFound
		}
		return nil, err
	}
	return &doc, nil
}

func (r *inviteRepository) ConsumeOne(ctx context.Context, id bson.ObjectID) (*entity.InviteCode, error) {
	now := time.Now().UTC().UnixMilli()
	filter := bson.M{
		authdomain.BSONFieldID: id,
		"$expr": bson.M{
			"$lt": bson.A{"$" + authdomain.BSONFieldUsedCount, "$" + authdomain.BSONFieldMaxUses},
		},
		"$or": bson.A{
			bson.M{authdomain.BSONFieldExpiresAt: bson.M{"$lte": 0}},
			bson.M{authdomain.BSONFieldExpiresAt: bson.M{"$gt": now}},
		},
	}
	update := bson.M{
		"$inc": bson.M{authdomain.BSONFieldUsedCount: 1},
		"$set": bson.M{authdomain.BSONFieldUpdateAt: now},
	}
	opts := options.FindOneAndUpdate().SetReturnDocument(options.After)
	var doc entity.InviteCode
	if err := r.db.GetClient().FindOneAndUpdate(ctx, &doc, filter, update, opts); err != nil {
		if errors.Is(err, mongodriver.ErrNoDocuments) {
			return nil, authdomain.ErrInviteExhausted
		}
		return nil, err
	}
	return &doc, nil
}

// Index20260521001UP ensures invite_codes collection indexes exist.
func (r *inviteRepository) Index20260521001UP(ctx context.Context) error {
	return r.db.PopulateMultiIndex(ctx,
		[]string{authdomain.BSONFieldTenantID, authdomain.BSONFieldCodeHash},
		[]int32{1, 1},
		true,
	)
}

var _ domrepo.InviteRepository = (*inviteRepository)(nil)
