package repository

import (
	"context"
	"errors"
	"time"

	libmongo "gateway/internal/library/mongo"
	permission "gateway/internal/model/permission/domain"
	"gateway/internal/model/permission/domain/entity"
	"gateway/internal/model/permission/domain/enum"
	domrepo "gateway/internal/model/permission/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"
)

// RoleMappingRepositoryParam configures the Mongo role mapping repository.
type RoleMappingRepositoryParam struct {
	Conf *libmongo.Conf
}

type roleMappingRepository struct {
	db libmongo.DocumentDBUseCase
}

// NewRoleMappingRepository creates a Mongo-backed RoleMappingRepository.
func NewRoleMappingRepository(param RoleMappingRepositoryParam) domrepo.RoleMappingRepository {
	documentDB, err := libmongo.NewDocumentDB(param.Conf, entity.RoleMapping{}.CollectionName())
	if err != nil {
		panic(err)
	}
	return &roleMappingRepository{db: documentDB}
}

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

func (r *roleMappingRepository) Upsert(ctx context.Context, rm *entity.RoleMapping) error {
	now := time.Now().UTC().UnixMilli()
	if rm.UpdateAt == 0 {
		rm.UpdateAt = now
	}
	filter := bson.M{
		permission.BSONFieldTenantID:       rm.TenantID,
		permission.BSONFieldExternalSource: rm.ExternalSource,
		permission.BSONFieldExternalKey:    rm.ExternalKey,
	}
	set := bson.M{
		permission.BSONFieldInternalRoleID:  rm.InternalRoleID,
		permission.BSONFieldInternalRoleKey: rm.InternalRoleKey,
		permission.BSONFieldUpdateAt:        rm.UpdateAt,
	}
	insert := bson.M{
		permission.BSONFieldTenantID:       rm.TenantID,
		permission.BSONFieldExternalSource: rm.ExternalSource,
		permission.BSONFieldExternalKey:    rm.ExternalKey,
		permission.BSONFieldCreateAt:       now,
	}
	_, err := r.db.GetClient().UpdateOne(ctx, filter,
		bson.M{bsonOpSet: set, bsonOpSetOnInsert: insert},
		options.UpdateOne().SetUpsert(true))
	return err
}

func (r *roleMappingRepository) Delete(
	ctx context.Context,
	tenantID string,
	source enum.RoleSource,
	externalKey string,
) error {
	filter := bson.M{
		permission.BSONFieldTenantID:       tenantID,
		permission.BSONFieldExternalSource: source,
		permission.BSONFieldExternalKey:    externalKey,
	}
	res, err := r.db.GetClient().DeleteOne(ctx, filter)
	if err != nil {
		return err
	}
	if res == 0 {
		return permission.ErrRoleMappingNotFound
	}
	return nil
}

func (r *roleMappingRepository) DeleteByRole(ctx context.Context, tenantID, roleID string) (int64, error) {
	filter := bson.M{
		permission.BSONFieldTenantID:       tenantID,
		permission.BSONFieldInternalRoleID: roleID,
	}
	return r.db.GetClient().DeleteMany(ctx, filter)
}

func (r *roleMappingRepository) GetByExternal(
	ctx context.Context,
	tenantID string,
	source enum.RoleSource,
	externalKey string,
) (*entity.RoleMapping, error) {
	filter := bson.M{
		permission.BSONFieldTenantID:       tenantID,
		permission.BSONFieldExternalSource: source,
		permission.BSONFieldExternalKey:    externalKey,
	}
	var doc entity.RoleMapping
	if err := r.db.GetClient().FindOne(ctx, &doc, filter); err != nil {
		if errors.Is(err, mongodriver.ErrNoDocuments) {
			return nil, permission.ErrRoleMappingNotFound
		}
		return nil, err
	}
	return &doc, nil
}

func (r *roleMappingRepository) ListByTenant(
	ctx context.Context,
	tenantID string,
	source *enum.RoleSource,
	offset, limit int64,
) ([]*entity.RoleMapping, int64, error) {
	q := bson.M{permission.BSONFieldTenantID: tenantID}
	if source != nil {
		q[permission.BSONFieldExternalSource] = *source
	}
	total, err := r.db.GetClient().CountDocuments(ctx, q)
	if err != nil {
		return nil, 0, err
	}
	if limit <= 0 {
		limit = 50
	}
	if limit > 200 {
		limit = 200
	}
	opts := options.Find().
		SetSkip(offset).
		SetLimit(limit).
		SetSort(bson.D{{Key: permission.BSONFieldCreateAt, Value: -1}})
	var docs []*entity.RoleMapping
	if err := r.db.GetClient().Find(ctx, &docs, q, opts); err != nil {
		return nil, 0, err
	}
	return docs, total, nil
}

// Index20260521001UP ensures role_mappings collection indexes exist.
func (r *roleMappingRepository) Index20260521001UP(ctx context.Context) error {
	if err := r.db.PopulateMultiIndex(ctx,
		[]string{
			permission.BSONFieldTenantID,
			permission.BSONFieldExternalSource,
			permission.BSONFieldExternalKey,
		},
		[]int32{1, 1, 1}, true); err != nil {
		return err
	}
	return r.db.PopulateMultiIndex(ctx,
		[]string{permission.BSONFieldTenantID, permission.BSONFieldInternalRoleID},
		[]int32{1, 1}, false)
}

var _ domrepo.RoleMappingRepository = (*roleMappingRepository)(nil)
