package template

import (
	"context"

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

type CascadeDeleterT = func(ctx context.Context, objectRef bson.ObjectID) error

type DBImp[T storable.Storable] struct {
	Logger     mlogger.Logger
	Repository repository.Repository
	cdeleter   CascadeDeleterT
}

func (db *DBImp[T]) Create(ctx context.Context, object T) error {
	return db.Repository.Insert(ctx, object, nil)
}

func (db *DBImp[T]) InsertMany(ctx context.Context, objects []T) error {
	if len(objects) == 0 {
		return nil
	}

	storables := make([]storable.Storable, len(objects))
	for i, obj := range objects {
		storables[i] = obj
	}

	return db.Repository.InsertMany(ctx, storables)
}

func (db *DBImp[T]) Get(ctx context.Context, objectRef bson.ObjectID, result T) error {
	return db.Repository.Get(ctx, objectRef, result)
}

func (db *DBImp[T]) Update(ctx context.Context, object T) error {
	return db.Repository.Update(ctx, object)
}

func (db *DBImp[T]) Patch(ctx context.Context, objectRef bson.ObjectID, patch builder.Patch) error {
	return db.Repository.Patch(ctx, objectRef, patch)
}

func (db *DBImp[T]) PatchMany(ctx context.Context, query builder.Query, patch builder.Patch) (int, error) {
	return db.Repository.PatchMany(ctx, query, patch)
}

func (db *DBImp[T]) Delete(ctx context.Context, objectRef bson.ObjectID) error {
	return db.Repository.Delete(ctx, objectRef)
}

func (db *DBImp[T]) DeleteMany(ctx context.Context, query builder.Query) error {
	return db.Repository.DeleteMany(ctx, query)
}

func (db *DBImp[T]) FindOne(ctx context.Context, query builder.Query, result T) error {
	return db.Repository.FindOneByFilter(ctx, query, result)
}

func (db *DBImp[T]) ListIDs(ctx context.Context, query builder.Query) ([]bson.ObjectID, error) {
	return db.Repository.ListIDs(ctx, query)
}

func (db *DBImp[T]) ListPermissionBound(ctx context.Context, query builder.Query) ([]model.PermissionBoundStorable, error) {
	return db.Repository.ListPermissionBound(ctx, query)
}

func (db *DBImp[T]) SetDeleter(deleter CascadeDeleterT) {
	db.Logger.Debug("Custom cascade deletion method installed")
	db.cdeleter = deleter
}

func (db *DBImp[T]) deleteCascadeStub(_ context.Context, objectRef bson.ObjectID) error {
	db.Logger.Warn("Unimplemented cascade delete call", mzap.ObjRef("object_ref", objectRef))
	return merrors.NotImplemented("custom implementation required")
}

func (db *DBImp[T]) DeleteCascade(ctx context.Context, objectRef bson.ObjectID) error {
	return db.cdeleter(ctx, objectRef)
}

func Create[T storable.Storable](
	logger mlogger.Logger,
	collection mservice.Type,
	db *mongo.Database,
) *DBImp[T] {
	res := &DBImp[T]{
		Logger:     logger.Named(collection),
		Repository: repository.CreateMongoRepository(db, collection),
	}
	res.cdeleter = res.deleteCascadeStub
	return res
}
