package store

import (
	"context"
	"testing"

	"github.com/tech/sendico/fx/storage/model"
	"github.com/tech/sendico/pkg/db/repository/builder"
	rd "github.com/tech/sendico/pkg/db/repository/decoder"
	ri "github.com/tech/sendico/pkg/db/repository/index"
	"github.com/tech/sendico/pkg/db/storable"
	"github.com/tech/sendico/pkg/db/transaction"
	"github.com/tech/sendico/pkg/merrors"
	pmodel "github.com/tech/sendico/pkg/model"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.mongodb.org/mongo-driver/v2/mongo"
)

type repoStub struct {
	insertFn     func(ctx context.Context, obj storable.Storable, filter builder.Query) error
	insertManyFn func(ctx context.Context, objects []storable.Storable) error
	findOneFn    func(ctx context.Context, query builder.Query, result storable.Storable) error
	findManyFn   func(ctx context.Context, query builder.Query, decoder rd.DecodingFunc) error
	updateFn     func(ctx context.Context, obj storable.Storable) error
	upsertFn     func(ctx context.Context, obj storable.Storable) error
	patchManyFn  func(ctx context.Context, filter builder.Query, patch builder.Patch) (int, error)
	createIdxFn  func(def *ri.Definition) error
}

func (r *repoStub) Aggregate(ctx context.Context, b builder.Pipeline, decoder rd.DecodingFunc) error {
	return merrors.NotImplemented("Aggregate not used")
}

func (r *repoStub) Insert(ctx context.Context, obj storable.Storable, filter builder.Query) error {
	if r.insertFn != nil {
		return r.insertFn(ctx, obj, filter)
	}
	return nil
}

func (r *repoStub) InsertMany(ctx context.Context, objects []storable.Storable) error {
	if r.insertManyFn != nil {
		return r.insertManyFn(ctx, objects)
	}
	return nil
}

func (r *repoStub) Get(ctx context.Context, id bson.ObjectID, result storable.Storable) error {
	return merrors.NotImplemented("Get not used")
}

func (r *repoStub) FindOneByFilter(ctx context.Context, query builder.Query, result storable.Storable) error {
	if r.findOneFn != nil {
		return r.findOneFn(ctx, query, result)
	}
	return nil
}

func (r *repoStub) FindManyByFilter(ctx context.Context, query builder.Query, decoder rd.DecodingFunc) error {
	if r.findManyFn != nil {
		return r.findManyFn(ctx, query, decoder)
	}
	return nil
}

func (r *repoStub) Update(ctx context.Context, obj storable.Storable) error {
	if r.updateFn != nil {
		return r.updateFn(ctx, obj)
	}
	return nil
}

func (r *repoStub) Upsert(ctx context.Context, obj storable.Storable) error {
	if r.upsertFn != nil {
		return r.upsertFn(ctx, obj)
	}
	return nil
}

func (r *repoStub) Patch(ctx context.Context, id bson.ObjectID, patch builder.Patch) error {
	return merrors.NotImplemented("Patch not used")
}

func (r *repoStub) PatchMany(ctx context.Context, filter builder.Query, patch builder.Patch) (int, error) {
	if r.patchManyFn != nil {
		return r.patchManyFn(ctx, filter, patch)
	}
	return 0, nil
}

func (r *repoStub) Delete(ctx context.Context, id bson.ObjectID) error {
	return merrors.NotImplemented("Delete not used")
}

func (r *repoStub) DeleteMany(ctx context.Context, query builder.Query) error {
	return merrors.NotImplemented("DeleteMany not used")
}

func (r *repoStub) CreateIndex(def *ri.Definition) error {
	if r.createIdxFn != nil {
		return r.createIdxFn(def)
	}
	return nil
}

func (r *repoStub) ListIDs(ctx context.Context, query builder.Query) ([]bson.ObjectID, error) {
	return nil, merrors.NotImplemented("ListIDs not used")
}

func (r *repoStub) ListPermissionBound(ctx context.Context, query builder.Query) ([]pmodel.PermissionBoundStorable, error) {
	return nil, merrors.NotImplemented("ListPermissionBound not used")
}

func (r *repoStub) ListAccountBound(ctx context.Context, query builder.Query) ([]pmodel.AccountBoundStorable, error) {
	return nil, merrors.NotImplemented("ListAccountBound not used")
}

func (r *repoStub) Collection() string { return "test" }

type txFactoryStub struct {
	executeFn func(ctx context.Context, cb transaction.Callback) (any, error)
}

func (f *txFactoryStub) CreateTransaction() transaction.Transaction {
	return &txStub{executeFn: f.executeFn}
}

type txStub struct {
	executeFn func(ctx context.Context, cb transaction.Callback) (any, error)
}

func (t *txStub) Execute(ctx context.Context, cb transaction.Callback) (any, error) {
	if t.executeFn != nil {
		return t.executeFn(ctx, cb)
	}
	return cb(ctx)
}

func cloneRate(t *testing.T, obj storable.Storable) *model.RateSnapshot {
	t.Helper()
	rate, ok := obj.(*model.RateSnapshot)
	if !ok {
		t.Fatalf("expected *model.RateSnapshot, got %T", obj)
	}
	copy := *rate
	return &copy
}

func cloneQuote(t *testing.T, obj storable.Storable) *model.Quote {
	t.Helper()
	quote, ok := obj.(*model.Quote)
	if !ok {
		t.Fatalf("expected *model.Quote, got %T", obj)
	}
	copy := *quote
	return &copy
}

func clonePair(t *testing.T, obj storable.Storable) *model.Pair {
	t.Helper()
	pair, ok := obj.(*model.Pair)
	if !ok {
		t.Fatalf("expected *model.Pair, got %T", obj)
	}
	copy := *pair
	return &copy
}

func cloneCurrency(t *testing.T, obj storable.Storable) *model.Currency {
	t.Helper()
	currency, ok := obj.(*model.Currency)
	if !ok {
		t.Fatalf("expected *model.Currency, got %T", obj)
	}
	copy := *currency
	return &copy
}

func runDecoderWithDocs(ctx context.Context, t *testing.T, decode rd.DecodingFunc, docs ...interface{}) error {
	t.Helper()
	cur, err := mongo.NewCursorFromDocuments(docs, nil, nil)
	if err != nil {
		t.Fatalf("failed to create cursor: %v", err)
	}
	defer func() {
		_ = cur.Close(ctx)
	}()

	if len(docs) > 0 {
		if !cur.Next(ctx) {
			return cur.Err()
		}
	}

	if err := decode(cur); err != nil {
		return err
	}
	return cur.Err()
}
