package repository

import (
	"context"
	"errors"
	"time"

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

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

// IdentityRepositoryParam configures the Mongo identity repository.
type IdentityRepositoryParam struct {
	Conf *libmongo.Conf
}

type identityRepository struct {
	db libmongo.DocumentDBUseCase
}

// NewIdentityRepository creates a Mongo-backed IdentityRepository.
func NewIdentityRepository(param IdentityRepositoryParam) domrepo.IdentityRepository {
	documentDB, err := libmongo.NewDocumentDB(param.Conf, entity.Identity{}.CollectionName())
	if err != nil {
		panic(err)
	}
	return &identityRepository{db: documentDB}
}

func (r *identityRepository) Insert(ctx context.Context, identity *entity.Identity) error {
	now := time.Now().UTC().UnixMilli()
	if identity.ID.IsZero() {
		identity.ID = bson.NewObjectID()
	}
	if identity.CreateAt == 0 {
		identity.CreateAt = now
	}
	if identity.UpdateAt == 0 {
		identity.UpdateAt = now
	}
	_, err := r.db.GetClient().InsertOne(ctx, identity)
	if err != nil {
		if mongodriver.IsDuplicateKeyError(err) {
			return member.ErrDuplicateMember
		}
		return err
	}
	return nil
}

func (r *identityRepository) GetByZitadelUserID(ctx context.Context, tenantID, zitadelUserID string) (*entity.Identity, error) {
	return r.findOne(ctx, bson.M{
		member.BSONFieldTenantID:      tenantID,
		member.BSONFieldZitadelUserID: zitadelUserID,
	})
}

func (r *identityRepository) GetByExternalID(ctx context.Context, tenantID, externalID string) (*entity.Identity, error) {
	return r.findOne(ctx, bson.M{
		member.BSONFieldTenantID:   tenantID,
		member.BSONFieldExternalID: externalID,
	})
}

func (r *identityRepository) GetByUID(ctx context.Context, tenantID, uid string) (*entity.Identity, error) {
	return r.findOne(ctx, bson.M{
		member.BSONFieldTenantID: tenantID,
		member.BSONFieldUID:      uid,
	})
}

func (r *identityRepository) findOne(ctx context.Context, filter bson.M) (*entity.Identity, error) {
	var doc entity.Identity
	if err := r.db.GetClient().FindOne(ctx, &doc, filter); err != nil {
		if errors.Is(err, mongodriver.ErrNoDocuments) {
			return nil, member.ErrNotFound
		}
		return nil, err
	}
	return &doc, nil
}

// Index20260520001UP ensures identities collection indexes exist.
func (r *identityRepository) Index20260520001UP(ctx context.Context) error {
	if err := r.db.PopulateMultiIndex(ctx,
		[]string{member.BSONFieldTenantID, member.BSONFieldZitadelUserID},
		[]int32{1, 1}, true); err != nil {
		return err
	}
	if err := r.db.PopulateMultiIndex(ctx,
		[]string{member.BSONFieldTenantID, member.BSONFieldUID},
		[]int32{1, 1}, false); err != nil {
		return err
	}
	return r.db.PopulateMultiIndex(ctx,
		[]string{member.BSONFieldTenantID, member.BSONFieldExternalID},
		[]int32{1, 1}, false)
}

var _ domrepo.IdentityRepository = (*identityRepository)(nil)
