package repository

import (
	"context"
	"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"
)

// UserRoleRepositoryParam configures the Mongo user-role repository.
type UserRoleRepositoryParam struct {
	Conf *libmongo.Conf
}

type userRoleRepository struct {
	db libmongo.DocumentDBUseCase
}

// NewUserRoleRepository creates a Mongo-backed UserRoleRepository.
func NewUserRoleRepository(param UserRoleRepositoryParam) domrepo.UserRoleRepository {
	documentDB, err := libmongo.NewDocumentDB(param.Conf, entity.UserRole{}.CollectionName())
	if err != nil {
		panic(err)
	}
	return &userRoleRepository{db: documentDB}
}

func (r *userRoleRepository) Insert(ctx context.Context, ur *entity.UserRole) error {
	now := time.Now().UTC().UnixMilli()
	if ur.ID.IsZero() {
		ur.ID = bson.NewObjectID()
	}
	if ur.Source == "" {
		ur.Source = enum.RoleSourceManual
	}
	if ur.CreateAt == 0 {
		ur.CreateAt = now
	}
	if ur.UpdateAt == 0 {
		ur.UpdateAt = now
	}
	_, err := r.db.GetClient().InsertOne(ctx, ur)
	if err != nil {
		if mongodriver.IsDuplicateKeyError(err) {
			return permission.ErrUserRoleDuplicate
		}
		return err
	}
	return nil
}

func (r *userRoleRepository) BulkInsert(ctx context.Context, urs []*entity.UserRole) error {
	if len(urs) == 0 {
		return nil
	}
	now := time.Now().UTC().UnixMilli()
	docs := make([]any, 0, len(urs))
	for _, ur := range urs {
		if ur.ID.IsZero() {
			ur.ID = bson.NewObjectID()
		}
		if ur.Source == "" {
			ur.Source = enum.RoleSourceManual
		}
		if ur.CreateAt == 0 {
			ur.CreateAt = now
		}
		if ur.UpdateAt == 0 {
			ur.UpdateAt = now
		}
		docs = append(docs, ur)
	}
	_, err := r.db.GetClient().InsertMany(ctx, docs)
	if err != nil && !mongodriver.IsDuplicateKeyError(err) {
		return err
	}
	return nil
}

func (r *userRoleRepository) Delete(ctx context.Context, tenantID, uid, roleID string) error {
	filter := bson.M{
		permission.BSONFieldTenantID: tenantID,
		permission.BSONFieldUID:      uid,
		permission.BSONFieldRoleID:   roleID,
	}
	res, err := r.db.GetClient().DeleteOne(ctx, filter)
	if err != nil {
		return err
	}
	if res == 0 {
		return permission.ErrUserRoleNotFound
	}
	return nil
}

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

func (r *userRoleRepository) ReplaceForSource(
	ctx context.Context,
	tenantID, uid string,
	source enum.RoleSource,
	roleIDs []string,
) error {
	filter := bson.M{
		permission.BSONFieldTenantID: tenantID,
		permission.BSONFieldUID:      uid,
		permission.BSONFieldSource:   source,
	}
	if _, err := r.db.GetClient().DeleteMany(ctx, filter); err != nil {
		return err
	}
	if len(roleIDs) == 0 {
		return nil
	}
	now := time.Now().UTC().UnixMilli()
	rows := make([]*entity.UserRole, 0, len(roleIDs))
	for _, rid := range roleIDs {
		rows = append(rows, &entity.UserRole{
			ID:       bson.NewObjectID(),
			TenantID: tenantID,
			UID:      uid,
			RoleID:   rid,
			Source:   source,
			CreateAt: now,
			UpdateAt: now,
		})
	}
	return r.BulkInsert(ctx, rows)
}

func (r *userRoleRepository) ListByUser(ctx context.Context, tenantID, uid string) ([]*entity.UserRole, error) {
	q := bson.M{
		permission.BSONFieldTenantID: tenantID,
		permission.BSONFieldUID:      uid,
	}
	var docs []*entity.UserRole
	if err := r.db.GetClient().Find(ctx, &docs, q); err != nil {
		return nil, err
	}
	return docs, nil
}

func (r *userRoleRepository) ListByRole(ctx context.Context, tenantID, roleID string) ([]*entity.UserRole, error) {
	q := bson.M{
		permission.BSONFieldTenantID: tenantID,
		permission.BSONFieldRoleID:   roleID,
	}
	var docs []*entity.UserRole
	if err := r.db.GetClient().Find(ctx, &docs, q); err != nil {
		return nil, err
	}
	return docs, nil
}

// Index20260521001UP ensures user_roles collection indexes exist.
func (r *userRoleRepository) Index20260521001UP(ctx context.Context) error {
	if err := r.db.PopulateMultiIndex(ctx,
		[]string{permission.BSONFieldTenantID, permission.BSONFieldUID, permission.BSONFieldRoleID},
		[]int32{1, 1, 1}, true); err != nil {
		return err
	}
	if err := r.db.PopulateMultiIndex(ctx,
		[]string{permission.BSONFieldTenantID, permission.BSONFieldRoleID},
		[]int32{1, 1}, false); err != nil {
		return err
	}
	return r.db.PopulateMultiIndex(ctx,
		[]string{permission.BSONFieldTenantID, permission.BSONFieldUID, permission.BSONFieldSource},
		[]int32{1, 1, 1}, false)
}

var _ domrepo.UserRoleRepository = (*userRoleRepository)(nil)
