// Package mutil provides utility functions for working with account-bound objects
// with permission enforcement.
//
// Example usage:
//
//	// Using the low-level repository approach
//	objects, err := mutil.GetAccountBoundObjects[model.ProjectFilter](
//		ctx, logger, accountRef, orgRef, model.ActionRead,
//		repository.Query(), &model.ViewCursor{Limit: &limit, Offset: &offset, IsArchived: &isArchived},
//		enforcer, repo)
//
//	// Using the AccountBoundDB interface approach
//	objects, err := mutil.GetAccountBoundObjectsFromDB[model.ProjectFilter](
//		ctx, logger, accountRef, orgRef,
//		repository.Query(), &model.ViewCursor{Limit: &limit, Offset: &offset, IsArchived: &isArchived},
//		accountBoundDB)
package mutil

import (
	"context"
	"errors"

	"github.com/tech/sendico/pkg/auth"
	"github.com/tech/sendico/pkg/db/repository"
	"github.com/tech/sendico/pkg/db/repository/builder"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"github.com/tech/sendico/pkg/model"
	mutil "github.com/tech/sendico/pkg/mutil/db"
	"github.com/tech/sendico/pkg/mutil/mzap"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.uber.org/zap"
)

// GetAccountBoundObjects retrieves account-bound objects with permission enforcement.
// This function handles the complex logic of:
// 1. Finding objects where accountRef matches OR is null/absent
// 2. Checking organization-level permissions for each object
// 3. Filtering to only objects the account has permission to read
func GetAccountBoundObjects[T any](
	ctx context.Context,
	logger mlogger.Logger,
	accountRef, organizationRef bson.ObjectID,
	filter builder.Query,
	cursor *model.ViewCursor,
	enforcer auth.Enforcer,
	repo repository.Repository,
) ([]T, error) {
	// Build query to find objects where accountRef matches OR is null/absent
	accountQuery := repository.WithOrg(accountRef, organizationRef)

	// Get all account-bound objects that match the criteria
	allObjects, err := repo.ListAccountBound(ctx, repository.ApplyCursor(accountQuery, cursor))
	if err != nil {
		if !errors.Is(err, merrors.ErrNoData) {
			logger.Warn("Failed to fetch account bound objects", zap.Error(err),
				mzap.AccRef(accountRef),
				mzap.ObjRef("organization_ref", organizationRef),
			)
		} else {
			logger.Debug("No matching account bound objects found", zap.Error(err),
				mzap.AccRef(accountRef),
				mzap.ObjRef("organization_ref", organizationRef),
			)
		}
		return nil, err
	}

	if len(allObjects) == 0 {
		return nil, merrors.NoData("no_account_bound_objects_found")
	}

	allowed := make([]bson.ObjectID, 0, len(allObjects))
	for _, ref := range allObjects {
		allowed = append(allowed, *ref.GetID())
	}
	if len(allowed) == 0 {
		return nil, merrors.NoData("no_data_found_or_allowed")
	}

	logger.Debug("Successfully retrieved account bound objects",
		zap.Int("total_count", len(allObjects)),
		mzap.AccRef(accountRef),
		mzap.ObjRef("organization_ref", organizationRef),
		zap.Any("objs", allObjects),
	)

	return mutil.GetObjects[T](ctx, logger, repository.Query().In(repository.IDField(), allowed), cursor, repo)
}
